I've got some JSON coming from firebase that looks like this
{
"type": "added",
"doc": {
"id": "asda98j1234jknkj3n",
"data": {
"title": "Foo",
"subtitle": "Baz"
}
}
}
Type can be one of "added"
, "modified"
or "removed"
. Doc
contains an id
and a data
field. The data
field can be any shape and I am able to decode that properly.
I want to use union types to represent these values like so,
type alias Doc data =
(String, data)
type DocChange doc
= Added doc
| Modified doc
| Removed doc
Here the Doc
type alias represents the value contained in the doc
field in the JSON above. DocChange
represents the whole thing. If the type is say "added"
, then the JSON must decode into Added doc
and so on. I don't understand how to decode union types.
I think the andThen
function from Json.Decode
looks like what I need, but I am unable to use it correctly.
First of all, it seems like you want to constrain the doc
parameter of DocChange
to a Doc
, so you should probably define it like this instead:
type DocChange data
= Added (Doc data)
| Modified (Doc data)
| Removed (Doc data)
Otherwise you'll have to repeatedly specify DocChange (Doc data)
in your functions type annotations which quickly gets annoying, and worse the more you nest it. In any case, I've continued using the types as you defined them:
decodeDocData : Decoder DocData
decodeDocData =
map2 DocData
(field "title" string)
(field "subtitle" string)
decodeDoc : Decoder data -> Decoder (Doc data)
decodeDoc dataDecoder =
map2 Tuple.pair
(field "id" string)
(field "data" dataDecoder)
decodeDocChange : Decoder data -> Decoder (DocChange (Doc data))
decodeDocChange dataDecoder =
field "type" string
|> andThen
(\typ ->
case typ of
"added" ->
map Added
(field "doc" (decodeDoc dataDecoder))
"modified" ->
map Modified
(field "doc" (decodeDoc dataDecoder))
"removed" ->
map Removed
(field "doc" (decodeDoc dataDecoder))
_ ->
fail ("Unknown DocChange type: " ++ typ)
)
The trick here is to decode "type"
first, then use andThen
to switch on it and choose the appropriate decoder. In this case the shape is identical across "types", but it may not be and this pattern gives the flexibility to handle diverging shapes as well. It could be simplified to just selecting the constructor and keeping the rest of the decoding common if you're absolutely sure they won't diverge.