Build json decoders and encoders simultaneously, conserving the need for functions that do each in simple cases. This allows a single datum to embody both the encoder and the decoder for a type, reducing some duplication and redundancy. In simple cases, a codec can be built exclusively by composing other codecs.
Contributions by francescortiz
In more complex cases, first could be used, which allows fields to be
encoded and extracted separately.
To just specify a decoder and encoder separately, use init.
module Session exposing (..)
import JsonCodec exposing (Codec)
import Dict exposing (Dict)
import Json.Decode as JD
import Json.Encode as JE
type alias Session =
{ queue : List String
, playing : Maybe (String, Float)
, likeCategories : Dict String Int
}
type Alternative = A | Unknown String
-- An example showing the use of ```map``` to augment a
-- bijective encoding.
altcoder : Codec Alternative
altcoder =
JsonCodec.string
|> JsonCodec.map
(\a -> if a == "A" then A else Unknown a)
(\v ->
case v of
A -> "A"
Unknown x -> x
)
type Picky = OptA | OptB
pickyOpts : Dict String Picky
pickyOpts = Dict.fromList [("A", OptA), ("B", OptB)]
-- An example showing the use of ```andThen``` to exclude
-- bad values.
pickycoder : Codec Picky
pickycoder =
JsonCodec.string |> JsonCodec.andThen
(((flip Dict.get) pickyOpts)
>> Maybe.map JD.succeed
>> Maybe.withDefault (JD.fail "Option must be A or B")
)
(\p -> pickyOpts
|> Dict.toList
|> List.filterMap (\(k,v) -> if v == p then Just k else Nothing)
|> List.head
|> Maybe.withDefault "A"
)
-- A codec that can stand on its own and also be reused.
playingSerializer : Codec (Maybe (String,Float))
playingSerializer =
JsonCodec.nullable
(JsonCodec.object2
(\a b -> (a,b))
("u", JsonCodec.string, Tuple.first)
("p", JsonCodec.float, Tuple.second)
)
-- Simple codec built with composition.
serializer : Codec Session
serializer =
JsonCodec.object3
Session
("queue", JsonCodec.list JsonCodec.string, .queue)
("playing", playingSerializer, .playing)
("like", JsonCodec.dict JsonCodec.int, .likeCategories)
-- Codec built in application style
type alias Test =
{ i : Int, b : Bool, f : Float, o : Maybe String, s : String }
codec =
Test
|> JC.first "i" JC.int .i
|> JC.next "b" JC.bool .b
|> JC.next "f" JC.float .f
|> JC.option "o" (JC.nullable JC.string) .o Nothing
|> JC.next "s" JC.string .s
|> JC.end
x = JD.decodeString (JC.decoder be)
"{\"i\":3,\"b\":false,\"f\":3.14,\"s\":\"hi there\"}"
-- Ok { b = False, f = 3.14, i = 3, o = Nothing, s = "hi there" }
y = JD.decodeString (JC.decoder codec)
"{\"i\":3,\"b\":false,\"f\":3.14,\"o\":\"hi\",\"s\":\"hi there\"}"
-- Ok { b = False, f = 3.14, i = 3, o = Just "hi", s = "hi there" }Type of a codec builder used with first, next and end
Codec that produces and consumes dictionaries of other values.
Codec that produces and consumes key value pair lists of other values.
Codec that matches a single field similar to Json.Decode and produces a singleton object with 1 field.
Codec that matches and produces objects with 2 given named fields.
Codec that matches and produces objects with 3 given named fields.
Codec that matches and produces objects with 4 given named fields.
Codec that matches and produces objects with 5 given named fields.
Codec that matches and produces objects with 6 given named fields.
Codec that matches and produces objects with 7 given named fields.
Codec that matches and produces objects with 8 given named fields.
Codec that produces a constant decoded value and encodes to a constant value. One might use this to check a field with a constant value, such as a version number.
Codec that produces a constant encoded value but always fails decoding. One might use this while prefiltering inputs based on their structure but ensure that encoded json has the right structure.
Wrap the codec in a transformer that produces and consumes another type.
type Alternative = A | Unknown String
altcoder : Codec Alternative
altcoder =
Codec.string
|> Codec.map
(\a -> if a == "A" then A else Unknown a)
(\v ->
case v of
A -> "A"
Unknown x -> x
)Like map, but the decode function returns a decoder that will be evaluated next, rather than just mapping the value.
-- An example showing the use of ```andThen``` to exclude
-- bad values.
pickycoder : Codec Picky
pickycoder =
JsonCodec.string |> JsonCodec.andThen
(((flip Dict.get) pickyOpts)
>> Maybe.map JD.succeed
>> Maybe.withDefault (JD.fail "Option must be A or B")
)
(\p -> pickyOpts
|> Dict.toList
|> List.filterMap (\(k,v) -> if v == p then Just k else Nothing)
|> List.head
|> Maybe.withDefault "A"
)Match one of many decoders, encode using the given function.
Construct an arbitrary codec from a decoder and an encoder function.
Start composing a codec to decode a record using a series of function applications.
Using:
v in that fieldv from the finished recordo)Return:
o, yields a Codec o.You can build record codecs of arbitrarily many parameters with this, the same way other codecs are built, together using the same code.
An example:
type alias X = { i : Int, s : String, b : Bool, f : Float }
c = JC.first "i" JC.int .i X
|> JC.next "s" JC.string .s
|> JC.next "b" JC.bool .b
|> JC.next "f" JC.float .f
|> JC.end
> JD.decodeString (JC.decoder c) "{\"i\":3,\"s\":\"hi\",\"b\":false,\"f\":1.9}"
Ok { i = 3, s = "hi", b = False, f = 1.9 } : Result.Result String Repl.XBegin decoding with an optional field. As first but a default value is added.
Continue a partial codec from first, satisfying one more parameter of the constructor function.
The type of codecs constructed by the library.
You can extract a Json.Decode.Decoder with
decoderand a function that constructsJson.Encoder.Valuewithencoder.