Convenience functions for working with Json
Examples assume the following imports:
import Json.Decode exposing (..)
import Date
import Set
import DictCan be helpful when decoding large objects incrementally.
See the andMap docs
for an explanation of how andMap works and how to use it.
Infix version of andMap that makes for a nice DSL when decoding objects.
See the (|:) docs
for an explanation of how (|:) works and how to use it.
Some JavaScript structures look like arrays, but aren't really. Examples
include HTMLCollection, NodeList and everything else that has a length
property, has values indexed by an integer key between 0 and length, but yet
is not a JavaScript Array.
This decoder can come to the rescue.
""" { "length": 3, "0": "foo", "1": "bar", "2": "baz" } """
|> decodeString (collection string)
--> Ok [ "foo", "bar", "baz" ]This function turns a list of decoders into a decoder that returns a list.
The returned decoder will zip the list of decoders with a list of values, matching each decoder with exactly one value at the same position. This is most often useful in cases when you find yourself needing to dynamically generate a list of decoders based on some data, and decode some other data with this list of decoders.
Note that this function, unlike List.map2's behaviour, expects the list of
decoders to have the same length as the list of values in the JSON.
sequence
[ map Just string
, succeed Nothing
, map Just string
]
|> flip decodeString """ [ "pick me", "ignore me", "and pick me" ] """
--> Ok [ Just "pick me", Nothing, Just "and pick me" ]Helps convering a list of decoders into a decoder for a list of that type.
decoders : List (Decoder String)
decoders =
[ field "foo" string
, field "bar" string
, field "another" string
]
""" { "foo": "hello", "another": "!", "bar": "world" } """
|> decodeString (combine decoders)
--> Ok [ "hello", "world", "!" ]Get access to the current index while decoding a list element.
repeatedStringDecoder : Int -> Decoder String
repeatedStringDecoder times =
string |> map (String.repeat times)
""" [ "a", "b", "c", "d" ] """
|> decodeString (indexedList repeatedStringDecoder)
--> Ok [ "", "b", "cc", "ddd" ]Get a list of the keys of a JSON object
""" { "alice": 42, "bob": 99 } """
|> decodeString keys
--> Ok [ "alice", "bob" ]Extract a set.
"[ 1, 1, 5, 2 ]"
|> decodeString (set int)
--> Ok <| Set.fromList [ 1, 2, 5 ]Extract a dict using separate decoders for keys and values.
""" { "1": "foo", "2": "bar" } """
|> decodeString (dict2 int string)
--> Ok <| Dict.fromList [ ( 1, "foo" ), ( 2, "bar" ) ]Try running the given decoder; if that fails, then succeed with the given fallback value.
""" { "children": "oops" } """
|> decodeString (field "children" (list string) |> withDefault [])
--> Ok []
""" null """
|> decodeString (field "children" (list string) |> withDefault [])
--> Ok []
""" 30 """
|> decodeString (int |> withDefault 42)
--> Ok 30If a field is missing, succeed with Nothing. If it is present, decode it
as normal and wrap successes in a Just.
When decoding with
maybe,
if a field is present but malformed, you get a success and Nothing.
optionalField gives you a failed decoding in that case, so you know
you received malformed data.
Examples:
Let's define a stuffDecoder that extracts the "stuff" field, if it exists.
stuffDecoder : Decoder (Maybe String)
stuffDecoder =
optionalField "stuff" stringIf the "stuff" field is missing, decode to Nothing.
""" { } """
|> decodeString stuffDecoder
--> Ok NothingIf the "stuff" field is present but not a String, fail decoding.
""" { "stuff": [] } """
|> decodeString stuffDecoder
--> Err "Expecting a String at _.stuff but instead got: []"If the "stuff" field is present and valid, decode to Just String.
""" { "stuff": "yay!" } """
|> decodeString stuffDecoder
--> Ok <| Just "yay!"Transform a result into a decoder
Sometimes it can be useful to use functions that primarily operate on
Result in decoders. An example of this is Json.Decode.Extra.date. It
uses the built-in Date.fromString to parse a String as a Date, and
then converts the Result from that conversion into a decoder which has
either already succeeded or failed based on the outcome.
validateString : String -> Result String String
validateString input =
case input of
"" ->
Err "Empty string is not allowed"
_ ->
Ok input
""" "something" """
|> decodeString (string |> andThen (fromResult << validateString))
--> Ok "something"
""" "" """
|> decodeString (string |> andThen (fromResult << validateString))
--> Err "I ran into a `fail` decoder: Empty string is not allowed"Extract an int using String.toInt
""" { "field": "123" } """
|> decodeString (field "field" parseInt)
--> Ok 123Extract a float using String.toFloat
""" { "field": "50.5" } """
|> decodeString (field "field" parseFloat)
--> Ok 50.5Extract a JSON-encoded string field
"Yo dawg, I heard you like JSON..."
If someone has put JSON in your JSON (perhaps a JSON log entry, encoded as a string) this is the function you're looking for. Give it a decoder and it will return a new decoder that applies your decoder to a string field and yields the result (or fails if your decoder fails).
logEntriesDecoder : Decoder (List String)
logEntriesDecoder =
doubleEncoded (list string)
logsDecoder : Decoder (List String)
logsDecoder =
field "logs" logEntriesDecoder
""" { "logs": "[\\"log1\\", \\"log2\\"]"} """
|> decodeString logsDecoder
--> Ok [ "log1", "log2" ]
Extract a date using
Date.fromString