Skip to content

Instantly share code, notes, and snippets.

@yang-wei yang-wei/
Last active Feb 13, 2020

What would you like to do?
Elm Json.Decode tutorial and cheatsheet

When receiving JSON data from other resources(server API etc), we need Json.Decode to convert the JSON values into Elm values. This gist let you quickly learn how to do that.

I like to follow working example code so this is how the boilerplate will look like:

import Graphics.Element exposing (Element, show)
import Task exposing (Task, andThen)
import Json.Decode exposing (Decoder, int, string, object3, (:=))

import Http

{- declare data type here -}
type alias SimpleRecord =
  { name : String
  , description : String

-- initialize mailbox with the type declared above
mailbox =
  Signal.mailbox (SimpleRecord "" "")


main : Signal Element
main = show mailbox.signal


fetchApi =
  Http.get decoder api 

handleResponse data =
  Signal.send mailbox.address data

-- decoder changes depends on our data type
decoder = ...

port run : Task Http.Error ()
port run =
  fetchApi `andThen` handleResponse

api =

So here we have a mailbox - mailbox which contains initial data depending on the data type. When the application starts, fetch is called and the response is handled by handleResponse. Our main function will display the value of mailbox value. Here, we will fetch data github api and display it on screen. So to follow along this tutorial, you have to modify

  1. data type (currently SimpleRecord)
  2. initial data in mailbox
  3. decoder function
  4. api (to get different data)

Notice that I am omitting some data annotations for brevity. Our main focus in this post will be decoder function. We will see how to utilize the Json.Decoder library when dealing with different type of data.


Object -> tuple

First of all let's fetch the elm-lang/core repository. The API looks like:

If you paste this on browser, you will see something like:

    "id": 25231002,
    "name": "core",
    "subscribers_count": 47

Let's say we are interested in 3 fields - name, description and watchers_count of this repository and we want to display it in simple tuple: ie (name, description, watchers_count).

Here is how our data type looks like:

type alias RepoTuple = ( String, String, Int )

mailbox =
  Signal.mailbox ("", "", 0)

We also need to declare the initial data in mailbox - ("", "", 0).

Because our API returns a JSON object, the Object fields in doc will give us what we need. In our case we need 3 fields, so we will use object3. Elm supports object1 to object8 and we have to decide depends on how many value we need.

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

The first argument (a -> b -> c -> value) is a function which takes 3 arguments and return a value. In Elm, we know that

(,,) "Name" "Description" 100
-- ("Name","Description",100) : ( String, String, number )

So our decoder would be:

repoTupleDecoder : Decoder RepoTuple
repoTupleDecoder =
    object3 (,,)
        ("name" := string)
        ("description" := string)
        ("watchers_count" := int)

The := operator is use to extract the field with the given name. Since we are interested in name, description and watchers_count so we declare it explicitly and state it's type.

fetchApi =
  Http.get repoTupleDecoder api 

And this is the working code for this example.

Object -> record

Often tuple doesn't give enough information, in most case we want to preserve the value of field itself. So our type will look like:

type alias RepoRecord =
  { name : String
  , description : String
  , watchers_count : Int

mailbox =
  Signal.mailbox (RepoRecord "" "" 0)

Note that in this case our data type is Record so we can initialize it by RepoRecord "" "" 0 which will return { name = "", description = "", watchers_count = 0 } which is cool. What cooler is we can even reuse it in our decoder:

repoRecordDecoder : Decoder RepoRecord
repoRecordDecoder =
    object3 RepoRecord
        ("name" := string)
        ("description" := string)
        ("watchers_count" := int)

Do remember to change the function name in fetchApi:

fetchApi =
  Http.get repoRecordDecoder api 

Now we will get a nice record:

{ name = "core", description = "Elm's core libraries", watchers_count = 338 }

Again, for your reference the source is here.

Nested Object

When we hit the API, it returns an object which contains the owner of repository in a nested object.

    "full_name": "elm-lang/core",
    "owner": {
        "login": "elm-lang",
        "id": 4359353,
        "avatar_url": ""

Let's look at how we can retrive the value in owner object. We can of course create nested decoder by using 2 times of object3 but we can avoid this by using [at](

at : List String -> Decoder a -> Decoder a

Changing everything we need:

type alias OwnerRecord =
  { login : String
  , id : Int
  , avatar_url : String

mailbox =
  Signal.mailbox (OwnerRecord "" -1 "")

Because the data we need have only single layer of nested field, so the first argument passed to at only has one value, that is ["owner"].

ownerDecoder : Decoder OwnerRecord
ownerDecoder =
    decoder = object3 OwnerRecord
                ("login" := string)
                ("id" := int)
                ("avatar_url" := string)
    at ["owner"] decoder

Any object -> List (Tuple)

Let's hit another API.

and it's result is:

    "Elm": 400423,
    "JavaScript": 352902,
    "CSS": 75013,
    "Haskell": 28719,
    "HTML": 965

In this case, we want all values.

type alias Languages =
  List (String, Int)

mailbox =
  Signal.mailbox []

Elm provides a very handy function - keyValuePairs

import import Json.Decode exposing (..., keyValuePairs)

;; ...

decoder : Decoder (List (String, Int))
decoder =
  keyValuePairs int

And we have all languages in tuple:

[ ("HTML", 965)
, ("Haskell", 28719)
, ("CSS", 75013)
, ("JavaScript", 352902)
, ("Elm", 400423)

Any object -> Dict

Besides keyValuePairs, we also has [Decoder.dict]( to turn object into dictionary.

type alias Languages =
  Dict String Int

mailbox =
  Signal.mailbox Dict.empty

decoder : Decoder (Dict String Int)
decoder =
  dict int


Array -> tuple

In the previous section, we had seen multiples way to deal with object typed JSON value. However sometimes, we have an array. For example:

This API returns repositories written in Elm.

"total_count": 1816,
"incomplete_results": false,
"items": [
        "id": 4475362,
        "name": "",
        "full_name": "elm-lang/",
        "id": 25231002,
        "name": "core",
        "full_name": "elm-lang/core",

The field we are particularly interested in is the items field. Let's say we want a list which contains the full_name of the Elm repository.

["elm-lang/", "elm-lang/core" ...]

Let's initialize our data first:

mailbox =
  Signal.mailbox []

First of all, here is our decoder to extract the full_name value

fullNameDecoder : Decoder String
fullNameDecoder =
  object1 identity ("full_name" := string)

Because our data is nested in the items field, we have to access it using the at operator (hope you still remember):

decoder =
  at ["items"] _

The items will give us an array which contains object, so we will use [Decoder.list](

list : Decoder a -> Decoder (List a)

Decoder.list takes a decoder and returns another decoder which can handle list. This suits our case:

decoder =
  at ["items"] (list fullNameDecoder)

Now if you wish to also extract other fields, you just have to change your fullNameDecoder. The source of this example is shown here.


This comment has been minimized.

Copy link

rjruizes commented May 19, 2016

Thanks for writing this!


This comment has been minimized.

Copy link

cybersiddhu commented May 20, 2016

Excellent 👍


This comment has been minimized.

Copy link

dahankzter commented Aug 19, 2016

How can we handle arbitrary json? What if The objects are unknown and we want to be able to load them and render them in a tree?


This comment has been minimized.


This comment has been minimized.

Copy link

yigitozkavci commented Oct 23, 2016

Thanks for this awesome gist.


This comment has been minimized.

Copy link

focusaurus commented Nov 27, 2016

Would you be willing to update this for elm 0.18? I believe the object2 et al functions have been renamed.


This comment has been minimized.

Copy link

sjfloat commented Mar 28, 2017

Yes, and tupleN is gone


This comment has been minimized.

Copy link

jonator commented Jun 30, 2017

I am getting the error Module 'Json.Decode' does not expose ':='


This comment has been minimized.

Copy link

sohaibiftikhar commented Apr 6, 2018

:= was replaced with field. See the docs.


This comment has been minimized.

Copy link

aeinbu commented Mar 24, 2019

object3 was replaced with map3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.