Thanks! I thought I’d also share some the more interesting parts of code:
Field value
We have a lot of different parts of an article we call a resource. Each resource has a particular structure: there’s a field called type. An example for that field might be type: 'Text'. We decode things differently based on that type. Typically, you may want to use Json.Decode.string along with Json.Decode.andThen in order to cover the different paths. That approach has some great upsides: you’ll use a case of statement which lets Elm tell you when you forget to add a case. For us, decoding the resource isn’t always as simple as just relying on the type. There’s multiple different types of Text, for example. We also have some resources which don’t live in the main collection: a main image resource, and a main text resource. So, we express all our decoders independently. They still need to be decode based on the type. We came up with this function which allows us to do exactly that:
import Json.Decode
{-| Successfully decodes a value when the decoded string matches the given value
fieldValue "yes" <| Json.Encode.string "yes"
--> Json.Decode.succeed "yes"
fieldValue "no" <| Json.Encode.string "yes"
--> Json.Decode.fail "Expected 'no' but got 'yes'"
Json.Decode.field "type" <| fieldValue "Text <| Json.Encode.object [ ("type", Json.Encode.string "Text") ]
--> Json.Decode.succeed "Text"
-}
fieldValue : String -> Json.Decode.Decoder String
fieldValue passingFieldValue =
Json.Decode.string
|> Json.Decode.andThen
(\value ->
if value == passingFieldValue then
Json.Decode.succeed value
else
"Expected '"
++ passingFieldValue
++ "' but got '"
++ value
++ "'"
|> Json.Decode.fail
)
Filtering unsupported values
Since we need to always render things and ignore parts we don’t support yet, we ended up making out main decoder look like this:
type Resource
= ImageResource Image
| TextResource Text
| Unsupported Json.Decode.Value
decodeResource : Json.Decode.Decoder Resource
decodeResource =
Json.Decode.oneOf
[ Json.Decode.map ImageResource decodeImage
, Json.Decode.map TextResource decodeText
, Json.Decode.map Unsupported Json.Decode.value
]
The idea here is that if the first three decoders fail, the last decoder gives us a way to ignore the failed resource, while still having it at hand for creating error reports. In development, if we need to make sure we cover all the cases, we can just comment out the Unsupported line!