Reputation: 1469
I try to decode some json in elm.
The object I´m receiving could be in two different shapes.
First case:
{
...
"ChipId": "NotSet"
...
}
Second Case:
{
...
"ChipId": {
"Item": "0000000000"
},
...
}
So the first one can easily be decoded with field "ChipId" string
but in case it´s the complex object it fails.
I already tried it with Decode.andThen
but I couldn't solve it.
Thank you for your help!
The way I tried was by using a Maybe
.
chipIdDecoder : Decoder String
chipIdDecoder =
let
chipIdIdDecoder : Decoder String
chipIdIdDecoder =
field "ChipId" (field "Fields" (firstElementDecoder string))
chooseChipId : Maybe String -> Decoder String
chooseChipId c =
case c of
Just id ->
case id of
"ChipId" ->
chipIdIdDecoder
_ ->
succeed ""
Nothing ->
fail "Chip Id is invalid"
in
nullable (field "ChipId" string)
|> andThen chooseChipId
I suppose the problem here is that Maybe
expects something or null
and not something or something else. ^^
Upvotes: 2
Views: 252
Reputation: 29136
tl;dr: use oneOf
A good approach to writing json decoders in Elm is to start from the smallest parts, write decoders that decode each of those parts independently, then move up to the next level and write a decoder for that by putting together the smaller pieces you've already made.
Here, for example, I would start by writing a decoder to handle the two possible forms of "ChipId"
separately. The first is just a string, which of course comes out of the box with elm/json
, so that's easy enough. The other is an object with a single field, which we'll just decode into a simple String
:
chipIdObjectDecoder : Decoder String
chipIdObjectDecoder =
field "Item" string
Then we need to put them together, which seems like the part you're struggling most with. Here the oneOf
function comes to our rescue, whose description says:
Try a bunch of different decoders. This can be useful if the JSON may come in a couple different formats.
Sounds like exactly what we need! To try both the string
decoder and our chipIdObjectDecoder
we can write:
eitherStringOrChipIdObject : Decoder String
eitherStringOrChipIdObject =
oneOf [ string, chipIdObjectDecoder ]
And then finally we need to decode the "ChipId"
field itself:
field "ChipId" eitherStringOrChipIdObject
All this put together in a single function:
chipIdDecoder : Decoder String
chipIdDecoder =
let
chipIdObjectDecoder : Decoder String
chipIdObjectDecoder =
field "Item" string
eitherStringOrChipIdObject : Decoder String
eitherStringOrChipIdObject =
oneOf [ string, chipIdObjectDecoder ]
in
field "ChipId" eitherStringOrChipIdObject
Or to simplify it a little bit, since the above is rather verbose:
chipIdDecoder : Decoder String
chipIdDecoder =
let
chipIdObjectDecoder =
field "Item" string
in
field "ChipId" (oneOf [ string, chipIdObjectDecoder ])
As a last note, since it's not clear whether your code is overly simplified. If the "ChipId"
object cannot be reduced to a simple string, you'll have to use a common type that can hold both a String
and a ChipIdObject
, and map
the decoded values to that common type. eitherStringOrChipIdObject
could then be something like this:
type alias ChipIdObject = { ... }
type ChipId
= ChipIdString String
| ChipIdObject ChipIdObject
eitherStringOrChipIdObject : Decoder ChipId
eitherStringOrChipIdObject =
oneOf
[ string |> map ChipIdString
, chipIdObjectDecoder |> map ChipIdObject
]
Upvotes: 7