Json.Decode

Turn JSON values into Gren values. We've inherited this from Elm. Definitely check out this intro to JSON decoders to get a feel for how this library works!

type Decoder a

A value that knows how to decode JSON values.

There is a whole section in guide.elm-lang.org about decoders, so check it out for a more comprehensive introduction!

string : Decoder String

Decode a JSON string into an Gren String.

decodeString string "true"              == Err ...
decodeString string "42"                == Err ...
decodeString string "3.14"              == Err ...
decodeString string "\"hello\""         == Ok "hello"
decodeString string "{ \"hello\": 42 }" == Err ...
bool : Decoder Bool

Decode a JSON boolean into an Gren Bool.

decodeString bool "true"              == Ok True
decodeString bool "42"                == Err ...
decodeString bool "3.14"              == Err ...
decodeString bool "\"hello\""         == Err ...
decodeString bool "{ \"hello\": 42 }" == Err ...
int : Decoder Int

Decode a JSON number into an Gren Int.

decodeString int "true"              == Err ...
decodeString int "42"                == Ok 42
decodeString int "3.14"              == Err ...
decodeString int "\"hello\""         == Err ...
decodeString int "{ \"hello\": 42 }" == Err ...
float : Decoder Float

Decode a JSON number into an Gren Float.

decodeString float "true"              == Err ..
decodeString float "42"                == Ok 42
decodeString float "3.14"              == Ok 3.14
decodeString float "\"hello\""         == Err ...
decodeString float "{ \"hello\": 42 }" == Err ...

Data Structures

nullable : Decoder a -> Decoder (Maybe a)

Decode a nullable JSON value into an Gren value.

decodeString (nullable int) "13"    == Ok (Just 13)
decodeString (nullable int) "42"    == Ok (Just 42)
decodeString (nullable int) "null"  == Ok Nothing
decodeString (nullable int) "true"  == Err ..
array : Decoder a -> Decoder (Array a)

Decode a JSON array into an Gren Array.

decodeString (array int) "[1,2,3]" == Ok [ 1, 2, 3 ]

decodeString (array bool) "[true,false]" == Ok [ True, False ]
dict : Decoder a -> Decoder (Dict String a)

Decode a JSON object into an Gren Dict.

decodeString (dict int) "{ \"alice\": 42, \"bob\": 99 }"
    == Ok (Dict.empty |> Dict.set "alice" 42 |> Dict.set "bob" 99)

If you need the keys (like "alice" and "bob") available in the Dict values as well, I recommend using a (private) intermediate data structure like Info in this example:

module User exposing ( User, decoder )

import Dict
import Json.Decode exposing (..)

type alias User =
    { name : String
    , height : Float
    , age : Int
    }

makeUser : String -> Float -> Int -> User
makeUser name height age =
    { name = name
    , height = height
    , age = age
    }

decoder : Decoder (Dict.Dict String User)
decoder =
    map (Dict.map infoToUser) (dict infoDecoder)

type alias Info =
    { height : Float
    , age : Int
    }

makeInfo : Float -> Int -> Info
makeInfo height age =
    { height = height
    , age = age
    }

infoDecoder : Decoder Info
infoDecoder =
    map2 makeInfo
        (field "height" float)
        (field "age" int)

infoToUser : String -> Info -> User
infoToUser name { height, age } =
    makeUser name height age

So now JSON like { "alice": { height: 1.6, age: 33 }} are turned into dictionary values like Dict.singleton "alice" (User "alice" 1.6 33) if you need that.

keyValuePairs : Decoder a -> Decoder (Array { key : String, value : a })

Decode a JSON object into an Gren Array of pairs.

decodeString (keyValuePairs int) "{ \"alice\": 42, \"bob\": 99 }"
    == Ok [ { key = "alice", value = 42 }, { key = "bob", value = 99 } ]
oneOrMore : (a -> Array a -> value) -> Decoder a -> Decoder value

Decode a JSON array that has one or more elements. This comes up if you want to enable drag-and-drop of files into your application. You would pair this function with elm/file to write a dropDecoder like this:

import File exposing (File)
import Json.Decoder as D

type Msg
    = GotFiles File (Array Files)

inputDecoder : D.Decoder Msg
inputDecoder =
    D.at [ "dataTransfer", "files" ] (D.oneOrMore GotFiles File.decoder)

This captures the fact that you can never drag-and-drop zero files.

Object Primitives

field : String -> Decoder a -> Decoder a

Decode a JSON object, requiring a particular field.

decodeString (field "x" int) "{ \"x\": 3 }" == Ok 3

decodeString (field "x" int) "{ \"x\": 3, \"y\": 4 }" == Ok 3

decodeString (field "x" int) "{ \"x\": true }"
    == Err
    ... decodeString (field "x" int) "{ \"y\": 4 }"
    == Err
    ... decodeString (field "name" string) "{ \"name\": \"tom\" }"
    == Ok "tom"

The object can have other fields. Lots of them! The only thing this decoder cares about is if x is present and that the value there is an Int.

Check out map2 to see how to decode multiple fields!

at : Array String -> Decoder a -> Decoder a

Decode a nested JSON object, requiring certain fields.

json = """{ "person": { "name": "tom", "age": 42 } }"""

decodeString (at ["person", "name"] string) json  == Ok "tom"
decodeString (at ["person", "age" ] int   ) json  == Ok 42

This is really just a shorthand for saying things like:

field "person" (field "name" string) == at [ "person", "name" ] string
index : Int -> Decoder a -> Decoder a

Decode a JSON array, requiring a particular index.

json = """[ "alice", "bob", "chuck" ]"""

decodeString (index 0 string) json  == Ok "alice"
decodeString (index 1 string) json  == Ok "bob"
decodeString (index 2 string) json  == Ok "chuck"
decodeString (index 3 string) json  == Err ...

Inconsistent Structure

maybe : Decoder a -> Decoder (Maybe a)

Helpful for dealing with optional fields. Here are a few slightly different examples:

json = """{ "name": "tom", "age": 42 }"""

decodeString (maybe (field "age"    int  )) json == Ok (Just 42)
decodeString (maybe (field "name"   int  )) json == Ok Nothing
decodeString (maybe (field "height" float)) json == Ok Nothing

decodeString (field "age"    (maybe int  )) json == Ok (Just 42)
decodeString (field "name"   (maybe int  )) json == Ok Nothing
decodeString (field "height" (maybe float)) json == Err ...

Notice the last example! It is saying we must have a field named height and the content may be a float. There is no height field, so the decoder fails.

Point is, maybe will make exactly what it contains conditional. For optional fields, this means you probably want it outside a use of field or at.

oneOf : Array (Decoder a) -> Decoder a

Try a bunch of different decoders. This can be useful if the JSON may come in a couple different formats. For example, say you want to read an array of numbers, but some of them are null.

import String

badInt : Decoder Int
badInt =
    oneOf [ int, null 0 ]

-- decodeString (array badInt) "[1,2,null,4]" == Ok [1,2,0,4]

Why would someone generate JSON like this? Questions like this are not good for your health. The point is that you can use oneOf to handle situations like this!

You could also use oneOf to help version your data. Try the latest format, then a few older ones that you still support. You could use andThen to be even more particular if you wanted.

Run Decoders

decodeString : Decoder a -> String -> Result Error a

Parse the given string into a JSON value and then run the Decoder on it. This will fail if the string is not well-formed JSON or if the Decoder fails for some reason.

decodeString int "4"     == Ok 4
decodeString int "1 + 2" == Err ...
decodeValue : Decoder a -> Value -> Result Error a

Run a Decoder on some JSON Value. You can send these JSON values through ports, so that is probably the main time you would use this function.

type alias Value = Value

Represents a JavaScript value.

type Error
= Field ({ name : String, error : Error })
| Index ({ index : Int, error : Error })
| OneOf (Array Error)
| Failure ({ message : String, value : Value })

A structured error describing exactly how the decoder failed. You can use this to create more elaborate visualizations of a decoder problem. For example, you could show the entire JSON object and show the part causing the failure in red.

errorToString : Error -> String

Convert a decoding error into a String that is nice for debugging.

It produces multiple lines of output, so you may want to peek at it with something like this:

import Html
import Json.Decode as Decode

errorToHtml : Decode.Error -> Html.Html msg
errorToHtml error =
    Html.pre [] [ Html.text (Decode.errorToString error) ]

Note: It would be cool to do nicer coloring and fancier HTML, but I wanted to avoid having an elm/html dependency for now. It is totally possible to crawl the Error structure and create this separately though!

Mapping

map : (a -> value) -> Decoder a -> Decoder value

Transform a decoder. Maybe you just want to know the length of a string:

import String

stringLength : Decoder Int
stringLength =
    map String.length string

It is often helpful to use map with oneOf, like when defining nullable:

nullable : Decoder a -> Decoder (Maybe a)
nullable decoder =
    oneOf
        [ null Nothing
        , map Just decoder
        ]
map2 :
(a -> b -> value)
-> Decoder a
-> Decoder b
-> Decoder value

Try two decoders and then combine the result. We can use this to decode objects with many fields:

type alias Point =
    { x : Float
    , y : Float
    }

makePoint : Float -> Float -> Point
makePoint x y =
    { x = x
    , y = y
    }

point : Decoder Point
point =
    map2 makePoint (field "x" float) (field "y" float)

-- decodeString point """{ "x": 3, "y": 4 }""" == Ok { x = 3, y = 4 }

It tries each individual decoder and puts the result together with the Point constructor.

map3 :
(a -> b -> c -> value)
-> Decoder a
-> Decoder b
-> Decoder c
-> Decoder value

Try three decoders and then combine the result. We can use this to decode objects with many fields:

type alias Person =
    { name : String, age : Int, height : Float }

makePerson : String -> Int -> Float -> Person
makePerson name age height =
    { name = name
    , age = age
    , height = height
    }

person : Decoder Person
person =
    map3 makePerson
        (at [ "name" ] string)
        (at [ "info", "age" ] int)
        (at [ "info", "height" ] float)

-- json = """{ "name": "tom", "info": { "age": 42, "height": 1.8 } }"""
-- decodeString person json == Ok { name = "tom", age = 42, height = 1.8 }

Like map2 it tries each decoder in order and then give the results to the Person constructor. That can be any function though!

map4 :
(a -> b -> c -> d -> value)
-> Decoder a
-> Decoder b
-> Decoder c
-> Decoder d
-> Decoder value
map5 :
(a -> b -> c -> d -> e -> value)
-> Decoder a
-> Decoder b
-> Decoder c
-> Decoder d
-> Decoder e
-> Decoder value
map6 :
(a -> b -> c -> d -> e -> f -> value)
-> Decoder a
-> Decoder b
-> Decoder c
-> Decoder d
-> Decoder e
-> Decoder f
-> Decoder value
map7 :
(a -> b -> c -> d -> e -> f -> g -> value)
-> Decoder a
-> Decoder b
-> Decoder c
-> Decoder d
-> Decoder e
-> Decoder f
-> Decoder g
-> Decoder value
map8 :
(a -> b -> c -> d -> e -> f -> g -> h -> value)
-> Decoder a
-> Decoder b
-> Decoder c
-> Decoder d
-> Decoder e
-> Decoder f
-> Decoder g
-> Decoder h
-> Decoder value

Fancy Decoding

lazy : ({} -> Decoder a) -> Decoder a

Sometimes you have JSON with recursive structure, like nested comments. You can use lazy to make sure your decoder unrolls lazily.

type alias Comment =
    { message : String
    , responses : Responses
    }

makeComment : String -> Responses -> Comment
makeComment message responses =
    { message = message
    , responses = responses
    }

type Responses
    = Responses (Array Comment)

comment : Decoder Comment
comment =
    map2 makeComment
        (field "message" string)
        (field "responses" (map Responses (array (lazy (\_ -> comment)))))

If we had said array comment instead, we would start expanding the value infinitely. What is a comment? It is a decoder for objects where the responses field contains comments. What is a comment though? Etc.

By using array (lazy (\_ -> comment)) we make sure the decoder only expands to be as deep as the JSON we are given. You can read more about recursive data structures here.

value : Decoder Value

Do not do anything with a JSON value, just bring it into Gren as a Value. This can be useful if you have particularly complex data that you would like to deal with later. Or if you are going to send it out a port and do not care about its structure.

null : a -> Decoder a

Decode a null value into some Gren value.

decodeString (null False) "null" == Ok False
decodeString (null 42) "null"    == Ok 42
decodeString (null 42) "42"      == Err ..
decodeString (null 42) "false"   == Err ..

So if you ever see a null, this will return whatever value you specified.

succeed : a -> Decoder a

Ignore the JSON and produce a certain Gren value.

decodeString (succeed 42) "true"    == Ok 42
decodeString (succeed 42) "[1,2,3]" == Ok 42
decodeString (succeed 42) "hello"   == Err ... -- this is not a valid JSON string

This is handy when used with oneOf or andThen.

fail : String -> Decoder a

Ignore the JSON and make the decoder fail. This is handy when used with oneOf or andThen where you want to give a custom error message in some case.

See the andThen docs for an example.

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

Create decoders that depend on previous results. If you are creating versioned data, you might do something like this:

info : Decoder Info
info =
    field "version" int
        |> andThen infoHelp

infoHelp : Int -> Decoder Info
infoHelp version =
    when version is
        4 ->
            infoDecoder4

        3 ->
            infoDecoder3

        _ ->
            fail <|
                "Trying to decode info, but version "
                    ++ toString version
                    ++ " is not supported."

-- infoDecoder4 : Decoder Info
-- infoDecoder3 : Decoder Info