Json.Decode

type Decoder a
a value that knows how to decode JSON values

type alias Value = Value
a JSON value

 

decodeValue : Decoder a -> Value -> Result String a
run a Decoder on some JSON value

JsonEncode.int 12 |> decodeValue int    == Ok 12 : Result String Int
    
decodeString : Decoder a -> String -> Result String a
parse the given string into a JSON value and then run the Decoder on it

decodeString int "4"     == Ok 4
decodeString int "1 + 2" == Err ...

 

string : Decoder String
a JSON string into an Elm 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
a JSON boolean into an Elm 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
a JSON number into an Elm 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
a JSON number into an Elm 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 ...

nullable : Decoder a -> Decoder (Maybe a)
a nullable JSON value into an Elm 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 ...

list : Decoder a -> Decoder (List a)
a JSON array into an Elm List

decodeString (list int) "[1,2,3]"       == Ok [1,2,3]
decodeString (list bool) "[true,false]" == Ok [True,False]

array : Decoder a -> Decoder (Array a)
a JSON array into an Elm Array

decodeString (array int) "[1,2,3]"       == Ok (Array.fromList [1,2,3])
decodeString (array bool) "[true,false]" == Ok (Array.fromList [True,False])

dict : Decoder a -> Decoder (Dict String a)
a JSON object into an Elm Dict

decodeString (dict int) "{ \"alice\": 42, \"bob\": 99 }"
  == Dict.fromList [("alice", 42), ("bob", 99)]

keyValuePairs : Decoder a -> Decoder (List (String, a))
a JSON object into an Elm List of pairs

decodeString (keyValuePairs int) "{ \"alice\": 42, \"bob\": 99 }"
== [("alice", 42), ("bob", 99)]

 

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"

at : List String -> Decoder a -> Decoder a
decode a nested JSON object, requiring certain fields

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

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

index : Int -> Decoder a -> Decoder a
decode a JSON array, requiring a particular index

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

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

maybe : Decoder a -> Decoder (Maybe a)

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

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

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

oneOf : List (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 (list badInt) "[1,2,null,4]" == Ok [1,2,0,4]

 

Mapping

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

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

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

point : Decoder Point
point = map2 Point (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:

type alias Person = { name : String, age : Int, height : Float }
  
person : Decoder Person
person =
  map3 Person
    (at ["name"] string)
    (at ["info","age"] int)
    (at ["info","height"] float)

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

map4 , map5 , map6 , map7 , map8

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
  }

type Responses = Responses (List Comment)

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

If we had said list 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 list (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 Elm as a Value. This can be useful if you have particularly crazy 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 Elm 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 Elm 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.

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 =
  case version of
    4 -> infoDecoder4
    3 -> infoDecoder3
    _ -> fail <| "Trying to decode info, but version " ++ toString version ++ " is not supported."

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