Json.Decode tricks: decode 8+ fields and nested sub-objects

Suppose you have some JSON that describes map markers:

{
  "name": "Rixos The Palm Dubai",
  "lat": 25.1212,
  "lon": 55.1535
}

Using Json.Decode, the decoder for this JSON looks like this:

decodeMarker : Json.Decode.Decoder Marker
decodeMarker =
    Json.Decode.map3 Marker
        (field "name" Json.Decode.string)
        (field "lat" Json.Decode.float)
        (field "lon" Json.Decode.float)

If your JSON acquires another field (say, marker color), you need to add one more field decoder ((field "color" Json.Decode.string)) and change map3 to map4 in the above decoder. But this can only extend so far as Json.Decode map functions only go up to map8.

Decoding 8+ fields

What if your JSON keeps accreting fields such that you end up with more than 8 fields? At this point, you could bust out Json.Decode.Pipeline which doesn't have this limitation. However, it's possible to decode a larger number of fields by combining the Json.Decode.map* calls as well.

Consider this flat JSON object with a larger number of fields (let's pretend there are 10 fields in total):

{
  "type": "marker",
  "name": "Rixos The Palm Dubai",
  "description": "Hotel",
  "color": "red",
  ...
  "lat": 25.1212,
  "lon": 55.1535
}

The decoder for this function can be broken up into two pieces

decodeMarker = 
    let 
        mainFields =
            Json.Decode.map8 Marker
                (field "type" Json.Decode.string)
                (field "name" Json.Decode.string)
                (field "description" Json.Decode.string)
                (field "color" Json.Decode.string)
                ...
    in
        Json.Decode.map3
            (<|)
            mainFields
            (field "lat" Json.Decode.float)
            (field "lon" Json.Decode.float)

Note that in the call to map3, instead of the record constructor, we have to use the function application operator, and then mainFields is followed by decoders for the remaining fields

It's also possible to break up the fields into more than two groups using this approach:

decodeMarker = 
    let 
        mainFields =
            Json.Decode.map2 Marker
                (field "type" Json.Decode.string)
                (field "name" Json.Decode.string)
                
        extraFields = 
            Json.Decode.map3 (<|)
                mainFields
                (field "description" Json.Decode.string)
                (field "color" Json.Decode.string)
    in
        Json.Decode.map3 (<|)
            extraFields
            (field "lat" Json.Decode.float)
            (field "lon" Json.Decode.float)

Nested sub-objects

Another complication you often have to deal with is nested sub-objects:

{
  "type": "marker",
  "location": {
    "lat": 25.1212,
    "lon": 55.1535
  },
  "attrs": {
    "name": "Rixos The Palm Dubai",
    "description": "Hotel"
  }
}

To decode this JSON into a record with a matching structure, you need to write two decoders to decode the fields in location and attrs and then combine them using one of the map* functions:

type alias Marker =
    { type : String
    , location : MarkerLocation
    , attrs : MarkerAttrs
    }

type alias MarkerLocation =
    { lat : Float
    , lon : Float
    }

type alias MarkerAttrs =
    { name : String
    , description : String
    }

decodeMarker : Json.Decode.Decoder Marker
decodeMarker =
    Json.Decode.map3 Marker
        (field "type" Json.Decode.string)
        (field "location" decodeMarkerLocation)
        (field "attrs" decodeMarkerAttrs)

decodeMarkerLocation : Json.Decode.Decoder MarkerLocation
decodeMarkerLocation =
    Json.Decode.map2 MarkerLocation
        (field "lat" Json.Decode.float)
        (field "lon" Json.Decode.float)

decodeMarkerAttrs : Json.Decode.Decoder MarkerAttrs
decodeMarkerAttrs =
    Json.Decode.map2 MarkerAttrs
        (field "name" Json.Decode.string)
        (field "description" Json.Decode.string)

This scales to multiple levels of nesting.

Note that JSON-to-Elm is able to generate such nested decoders for you.


I talk more about JSON decoding and other Elm topics in my book, Practical Elm. Scroll down to grab a sample chapter (by the way, the book is available for a lower intro price until 26 June 2018).

Comments or questions? I'm @alexkorban on Twitter.