elm-community / json-extra / Json.Decode.Extra

Convenience functions for working with Json

Dates

datetime : Json.Decode.Decoder Time.Posix

Decode an ISO-8601 formatted date-time string.

This always returns a Time.Posix value, which is naturally always expressed in UTC.

import Json.Decode exposing (..)
import Json.Encode
import Time

""" "2018-08-26T09:46:00+02:00" """
    |> decodeString datetime
--> Ok (Time.millisToPosix 1535269560000)

""" "" """
  |> decodeString datetime
--> Err
-->    (Failure
-->        "Expecting an ISO-8601 formatted date+time string"
-->        (Json.Encode.string "")
-->    )

URLs

url : Json.Decode.Decoder Url

Decode a URL

This always returns a Url.Url value.

import Json.Decode exposing (..)
import Url

""" "http://foo.bar/quux" """
    |> decodeString url
--> Ok <| Url.Url Url.Http "foo.bar" Nothing "/quux" Nothing Nothing

Incremental Decoding

andMap : Json.Decode.Decoder a -> Json.Decode.Decoder (a -> b) -> Json.Decode.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.

Conditional Decoding

when : Json.Decode.Decoder a -> (a -> Basics.Bool) -> Json.Decode.Decoder b -> Json.Decode.Decoder b

Helper for conditionally decoding values based on some discriminator that needs to pass a certain check.

import Json.Decode exposing (..)
import Json.Encode


is : a -> a -> Bool
is a b =
    a == b


enabledValue : Decoder Int
enabledValue =
  (field "value" int)
    |> when (field "enabled" bool) (is True)


""" { "enabled": true, "value": 123 } """
    |> decodeString enabledValue
--> Ok 123


input : Json.Decode.Value
input =
    Json.Encode.object
        [ ( "enabled", Json.Encode.bool False )
        , ( "value", Json.Encode.int 321 )
        ]

expectedError : Error
expectedError =
   Failure "Check failed with input" input

""" { "enabled": false, "value": 321 } """
    |> decodeString enabledValue
--> Err expectedError

This can also be used to decode union types that are encoded with a discriminator field:

type Animal = Cat String | Dog String


dog : Decoder Animal
dog =
    map Dog (field "name" string)


cat : Decoder Animal
cat =
    map Cat (field "name" string)


animalType : Decoder String
animalType =
    field "type" string


animal : Decoder Animal
animal =
    oneOf
        [ when animalType (is "dog") dog
        , when animalType (is "cat") cat
        ]


"""
[
  { "type": "dog", "name": "Dawg" },
  { "type": "cat", "name": "Roxy" }
]
"""
    |> decodeString (list animal)
--> Ok [ Dog "Dawg", Cat "Roxy" ]

List

collection : Json.Decode.Decoder a -> Json.Decode.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.

import Json.Decode exposing (..)


""" { "length": 3, "0": "foo", "1": "bar", "2": "baz" } """
    |> decodeString (collection string)
--> Ok [ "foo", "bar", "baz" ]

sequence : List (Json.Decode.Decoder a) -> Json.Decode.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.

import Json.Decode exposing (..)


decoder : Decoder (List (Maybe String))
decoder =
    sequence
        [ map Just string
        , succeed Nothing
        , map Just string
        ]


decodeString decoder """ [ "pick me", "ignore me", "and pick me" ] """
--> Ok [ Just "pick me", Nothing, Just "and pick me" ]

combine : List (Json.Decode.Decoder a) -> Json.Decode.Decoder (List a)

Helps converting a list of decoders into a decoder for a list of that type.

import Json.Decode exposing (..)


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 : (Basics.Int -> Json.Decode.Decoder a) -> Json.Decode.Decoder (List a)

Get access to the current index while decoding a list element.

import Json.Decode exposing (..)


repeatedStringDecoder : Int -> Decoder String
repeatedStringDecoder times =
    string |> map (String.repeat times)


""" [ "a", "b", "c", "d" ] """
    |> decodeString (indexedList repeatedStringDecoder)
--> Ok [ "", "b", "cc", "ddd" ]

keys : Json.Decode.Decoder (List String)

Get a list of the keys of a JSON object

import Json.Decode exposing (..)


""" { "alice": 42, "bob": 99 } """
    |> decodeString keys
--> Ok [ "alice", "bob" ]

Set

set : Json.Decode.Decoder comparable -> Json.Decode.Decoder (Set comparable)

Extract a set.

import Json.Decode exposing (..)
import Set


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

Dict

dict2 : Json.Decode.Decoder comparable -> Json.Decode.Decoder v -> Json.Decode.Decoder (Dict comparable v)

Extract a dict using separate decoders for keys and values.

import Json.Decode exposing (..)
import Dict


""" { "1": "foo", "2": "bar" } """
    |> decodeString (dict2 int string)
--> Ok <| Dict.fromList [ ( 1, "foo" ), ( 2, "bar" ) ]

Maybe

withDefault : a -> Json.Decode.Decoder a -> Json.Decode.Decoder a

Try running the given decoder; if that fails, then succeed with the given fallback value.

import Json.Decode exposing (..)


""" { "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 -> Json.Decode.Decoder a -> Json.Decode.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:

import Json.Decode exposing (..)
import Json.Encode

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.

expectedError : Error
expectedError =
    Failure "Expecting a STRING" (Json.Encode.list identity [])
      |> Field "stuff"

""" { "stuff": [] } """
    |> decodeString stuffDecoder
--> Err expectedError

If the "stuff" field is present and valid, decode to Just String.

""" { "stuff": "yay!" } """
    |> decodeString stuffDecoder
--> Ok <| Just "yay!"

optionalNullableField : String -> Json.Decode.Decoder a -> Json.Decode.Decoder (Maybe a)

A neat combination of optionalField and nullable.

What this means is that a decoder like optionalNullableField "foo" string will return Just "hello" for {"foo": "hello"}, Nothing for both {"foo": null} and {}, and an error for malformed input like {"foo": 123}.

import Json.Decode exposing (Decoder, Error(..), decodeString, field, string)
import Json.Decode.Extra exposing (optionalNullableField)
import Json.Encode

myDecoder : Decoder (Maybe String)
myDecoder =
    optionalNullableField "foo" string


""" {"foo": "hello"} """
    |> decodeString myDecoder
--> Ok (Just "hello")


""" {"foo": null} """
    |> decodeString myDecoder
--> Ok Nothing


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


""" {"foo": 123} """
    |> decodeString myDecoder
    |> Result.mapError (\_ -> "expected error")
--> Err "expected error"

fromMaybe : String -> Maybe a -> Json.Decode.Decoder a

Transform a Maybe a into a Decoder a

Sometimes, you'll have a function that produces a Maybe a value, that you may want to use in a decoder.

Let's say, for example, that we have a function to extract the first letter of a string, and we want to use that in a decoder so we can extract only the first letter of that string.

import Json.Decode exposing (..)
import Json.Encode


firstLetter : String -> Maybe Char
firstLetter input =
    Maybe.map Tuple.first (String.uncons input)


firstLetterDecoder : Decoder Char
firstLetterDecoder =
    andThen
        (fromMaybe "Empty string not allowed" << firstLetter)
        string

""" "something" """
    |> decodeString firstLetterDecoder
--> Ok 's'


""" "" """
    |> decodeString firstLetterDecoder
--> Err (Failure "Empty string not allowed" (Json.Encode.string ""))

Result

fromResult : Result String a -> Json.Decode.Decoder a

Transform a result into a decoder

Sometimes it can be useful to use functions that primarily operate on Result in decoders.

import Json.Decode exposing (..)
import Json.Encode


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 (Failure "Empty string is not allowed" (Json.Encode.string ""))

Encoded strings

parseInt : Json.Decode.Decoder Basics.Int

Extract an int using String.toInt

import Json.Decode exposing (..)


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

parseFloat : Json.Decode.Decoder Basics.Float

Extract a float using String.toFloat

import Json.Decode exposing (..)


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

doubleEncoded : Json.Decode.Decoder a -> Json.Decode.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).

import Json.Decode exposing (..)


logEntriesDecoder : Decoder (List String)
logEntriesDecoder =
    doubleEncoded (list string)


logsDecoder : Decoder (List String)
logsDecoder =
    field "logs" logEntriesDecoder


""" { "logs": "[\\"log1\\", \\"log2\\"]"} """
    |> decodeString logsDecoder
--> Ok [ "log1", "log2" ]