I need to read from a RSS feed that contains <enclosure> element in Elm. While I can conveniently use YQL to get the JSON version of the feed directly, they comes in different shapes:

//case 1: As object { ... enclosure: { url: 'http://...' , length:12345, type:'audio/mpeg' } ... } //case 2: As list, I'll take the first one { ... enclosure : [ { url:'http://...', length:12345, type:'audio/mpeg'} , { url:'http://...', length:12345, type:'audio/mpeg'} , ... ] } //case 3: enclosure with type other than audio will be filtered out { ... enclosure: { url: 'http://...' , length:12345, type:'video/mp4' } ... } //case 4: no enclosure, this will be filtered out too { ... }

To decode it into type like this:

type alias Model = { list : List Item } type alias Item = { url : String }

We can do this one by one and compose it together. First we do the simple case of just decode the url and type as tuple for further processing.

import Json.Decode as Json decodeSingleEnclosure : Json.Decoder (String, String) decodeSingleEnclosure = Json.object2 (,) ("url" := Json.string) ("type" := Json.string)

We need to do a validation on that enclosure type as in case 3. We will use andThen to do it.

decodeSingleEnclosureUrl : Json.Decoder String decodeSingleEnclosureUrl = decodeSingleEnclosure `Json.andThen` (\(url, type_) -> if Regex.contains (Regex.regex "^audio/") type_ then Json.succeed url else Json.fail "not audio type" )

To decode the list of enclosure in case 2, we first decode it as list, andThen take the first item. Note that we use maybe to allow failed decoding in list and filter it using filterMap .

decodeEnclosureListUrl : Json.Decoder String decodeEnclosureListUrl = Json.list (Json.maybe decodeSingleEnclosureUrl) `Json.andThen` (\list -> -- List (Maybe String) let head = list |> List.filterMap identity |> List.head in case head of Just first -> Json.succeed first Nothing -> Json.fail "cannot get enclosure" )

Now we can use oneOf to deal with both case 1 and 2

decodeEnclosureUrl : Json.Decoder String decodeEnclosureUrl = Json.oneOf [ "enclosure" := decodeSingleEnclosureUrl , "enclosure" := decodeEnclosureListUrl ]

Fianlly, we deocde that url into list item. If no URL is found, the item is skipped

decodeItem: Json.Decoder (Maybe Item) decodeItem = Json.maybe (Json.object1 Item decodeEnclosureUrl) decodeModel : Json.Decoder Model decodeModel = Json.map Model ( ("items" := Json.list decodeItem) `Json.andThen` (\list -> list |> List.filterMap identity |> Json.succeed ) )

Check the whole exmaple at this gist. You can copy it to elm-lang.org/try to run it.

Decoding complex JSON in Elm is something I found quite hard at first. I need to wrap my head around and get familiar with the whole decoder concept. Again, function composition is the main key to deal with complex JSON. Try to arrive at the simplest case first and then gradually build on top of that.