elm-community/json-extra/2.4.0

NOTE: this package is not compatible with Elm 0.19.1

Json.Decode.Extra

Convenience functions for working with Json

Examples assume the following imports:

import Json.Decode exposing (..)
import Date
import Set
import Dict

Date

date : Decoder Date

Extract a date using Date.fromString

""" "2012-04-23T18:25:43.511Z" """
    |> decodeString date
--> Date.fromString "2012-04-23T18:25:43.511Z"


""" "foo" """
    |> decodeString date
--> Err "I ran into a `fail` decoder: Unable to parse 'foo' as a date. Dates must be in the ISO 8601 format."

Incremental Decoding

andMap : Decoder a -> Decoder (a -> b) -> Decoder b

Can be helpful when decoding large objects incrementally.

See the andMap docs for an explanation of how andMap works and how to use it.

(|:) : Decoder (a -> b) -> Decoder a -> Decoder b

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.

List

collection : Decoder a -> Decoder (List a)

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" ]
sequence : List (Decoder a) -> Decoder (List a)

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" ]
combine : List (Decoder a) -> Decoder (List a)

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", "!" ]
indexedList : (Int -> Decoder a) -> Decoder (List a)

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" ]

Set

set : Decoder comparable -> Decoder (Set comparable)

Extract a set.

"[ 1, 1, 5, 2 ]"
    |> decodeString (set int)
--> Ok <| Set.fromList [ 1, 2, 5 ]

Dict

dict2 : Decoder comparable -> Decoder v -> Decoder (Dict comparable v)

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" ) ]

Maybe

withDefault : a -> Decoder a -> Decoder a

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 30
optionalField : String -> Decoder a -> Decoder (Maybe a)

If 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" string

If the "stuff" field is missing, decode to Nothing.

""" { } """
    |> decodeString stuffDecoder
--> Ok Nothing

If 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!"

Result

fromResult : Result String a -> Decoder a

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"

Encoded strings

parseInt : Decoder Int

Extract an int using String.toInt

""" { "field": "123" } """
    |> decodeString (field "field" parseInt)
--> Ok 123
parseFloat : Decoder Float

Extract a float using String.toFloat

""" { "field": "50.5" } """
    |> decodeString (field "field" parseFloat)
--> Ok 50.5
doubleEncoded : Decoder a -> Decoder a

Extract 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" ]