Skip to content

robx/elm-edn

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

De- and encoding of EDN

EDN (Extensible Data Notation) is a Clojure-derived data transfer format. This project aims to provide EDN decoders and encoders for Elm.

The Decode and Encode modules are modeled on the standard library's Json.Decode and Json.Encode

Examples

First, two EDN messages, as used in siren.

statusMsg = """
#siren/status             ; a tag, applied to the following map
    { :state "play"       ; (keyword, string) map entry
    , :elapsed 11.342     ; a floating point number
    , :volume 30          ; an integer
    }
"""

playlistMsg = """
; compact message, two-element list
#siren/playlist({:pos 0 :track"01.mp3"}{:pos 1 :track"02.mp3"})
"""

You can write a decoder just like you would with Json.Decode.

import Decode

type alias Status =
    { state : String
    , elapsed : Float
    , volume : Int
    }

type alias PlaylistTrack =
    { pos : Int
    , track : String
    }

type Message =
    = MsgStatus Status
    | MsgPlaylist (List PlaylistTrack)

msgDecoder : Decode.Decoder Message
msgDecoder = Decode.tagged
    [ ( "siren/status"
      , Decode.map MsgStatus <|
          Decode.map3 Status
              (Decode.field "state" Decode.string)
              (Decode.field "elapsed" Decode.float)
              (Decode.field "volume" Decode.int)
      )
    , ( "siren/playlist"
      , Decode.map MsgPlaylist <|
          Decode.list <|
              Decode.map2 PlaylistTrack
                  (Decode.field "pos" Decode.int)
                  (Decode.field "track" Decode.string)
      )
    ]

Decode.decodeString msgDecoder statusMsg
--> Ok (MsgStatus {state = "play", elapsed = 11.342, volume = 30})

Or encode much like Json.Encode:

encodeMsg : Message -> Encode.Element
encodeMsg msg = case msg of
    MsgStatus status ->
        Encode.mustTagged "siren/status" <|
            Encode.mustObject
                [ ( "state", Encode.string status.state )
                , ( "elapsed", Encode.float status.elapsed )
                , ( "volume", Encode.int status.volume )
                ]

    MsgPlaylist tracks ->
        let
            encodeTrack track = Encode.mustObject
                [ ( "pos", Encode.int track.pos )
                , ( "track", Encode.string track.track )
                ]
        in
        Encode.mustTagged "siren/playlist" <|
            Encode.list (List.map encodeTrack tracks)

Encode.encode <| encodeMsg <| MsgStatus
    { state = "pause"
    , elapsed = 0
    , volume = 55
    }
--> "#siren/status {:state "pause", :elapsed 0.0, :volume 55}"

Status

It's all still a bit rough around the edges, but the parsing and decoding should be mostly complete. Some element types are parsed correctly but not yet exposed through Decode, e.g. arbitrary precision numbers.

The encoding module is very much minimal effort so far, falling back to possibly incorrect string and number formatting primitives.