Search code examples
jsonpipelineelmhateoasdecoder

In Elm, how to decode a JSON object inside nested JSON


I use Elm 0.19.1 with NoRedInk/elm-json-decode-pipeline/1.0.0

I have an Aircraft type which is

type alias Aircraft = {name:String}

For this, I have the following decoder:

aircraftDecoder : Json.Decode.Decoder Aircraft        
aircraftDecoder =            
    Json.Decode.succeed Aircraft            
    |> Json.Decode.Pipeline.required "name" Json.Decode.string

Unfortunately, the decoder is complaining me telling: "BadBody "Problem with the given value: (...)" This is because actually my area of interest if full of noise around it (from an HATEOAS api), like this:

{
  "_embedded" : {
    "aircrafts" : [ {
      "name" : "AC01",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/aircrafts/1"
        },
        "aircraft" : {
          "href" : "http://localhost:8080/aircrafts/1"
        }
      }
    }, {
      "name" : "AC01",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/aircrafts/2"
        },
        "aircraft" : {
          "href" : "http://localhost:8080/aircrafts/2"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/aircrafts{?page,size,sort}",
      "templated" : true
    },
    "profile" : {
      "href" : "http://localhost:8080/profile/aircrafts"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 4,
    "totalPages" : 1,
    "number" : 0
  }
}

How can I change the code, and keep using the Pipeline, so that the decoder doesn't get lost by all this noise?

I heard something about using Json.Decode.at, but the documentation is not good enough to let me get the correct code.


Solution

  • As far as I can tell, the problem is that:

    1. at this moment, your decoder might be well fit to decode a single Aircraft
    2. however, your JSON has a list of Aircrafts
    3. also, you decoder does not know where, in the JSON, it might find Aircrafts

    And you know, for Elm Aircraft and List Aircraft are completely different beats (as they should be). Anyway, the solution would be a two-step thing:

    1. Telling the decoder where, within the JSON structure, are the Aircrafts
    2. Parse a List Aircraft instead of a single Aircraft

    Following your code and importing at and list from Json.Decode, the code might look like that:

    listAircraftDecoder : Json.Decode.Decoder List Aircraft 
    listAircraftDecoder =
        at ["_embedded", "aircrafts"] (list aircraftDecoder)
    

    This means we're now aiming at a list of Aircrafts and that this list is an array within the JSON. Starting at the JSON root, take the property "_embedded" and, inside it, the property "aircrafts". This is a array, and Elm's list knows how to deal with it.

    Finally, we just need to tell Elm's list to pass each element of the JSON array to a specific decoder – your aircraftDecoder in our case.

    Does that make sense?