NoDisplayName
NoDisplayName

Reputation: 15746

Decode JSON where value can be a string or an array of different values

Yet another question on how to decode things with Elm...

So the problem is that I need to decode a value which can be either a string, for example

"price_unknown"

or it can be an array of 2 elements, where first one is a string and the second is a float:

["price", 50.5]

And for the eventual value I have a type:

type Something
  = PriceUnknown
  = Price Float

into which I need to convert the value from the json response.

I tried to use a bunch of things using somewhat between the lines of:

decode MyString
  |> required "other_value" Json.Decode.string
  |> required "price" JD.oneOf [JD.string, JD.list (JD.oneOf [JD.string, JD.float])] |> JD.map mapper

( I use json_decode_pipeline package here )

But obviously it complains about different values in the lists and whatnot so I am stuck.

Thank you in advance.

Upvotes: 1

Views: 261

Answers (1)

Gilbert Kennen
Gilbert Kennen

Reputation: 46

You are pretty close, but all of the Decoders in oneOf have to have the same type. Also, destructuring mixed lists can be a pain. This uses elm-community/json-extra to make a manual decoding step easier.

myDecoder : Decoder SomethingElse
myDecoder =
    decode MyString
        |> required "other_value" Json.Decode.string
        |> required "price" priceDecoder


priceDecoder : Decoder Something
priceDecoder =
    JD.oneOf
        [ priceUnknownDecoder
        , priceKnownDecoder
        ]


priceUnknownDecoder : Decoder Something
priceUnknownDecoder =
    JD.string
        |> JD.andThen
            (\string ->
                if string == "price_unknown" then
                    JD.succeed PriceUnknown
                else
                    JD.fail "Invalid unknown price string."
            )


priceKnownDecoder : Decoder Something
priceKnownDecoder =
    listTupleDecoder
        JD.string
        JD.float
        |> JD.andThen
            (\(tag, price) ->
                if tag == "price" then
                    JD.succeed (Price price)
                else
                    JD.fail "Invalid tag string."
            )


listTupleDecoder : Decoder a -> Decoder b -> Decoder (a, b)
listTupleDecoder firstD secondD =
    JD.list JD.value
        |> JD.andThen
            (\values ->
                case values of
                    [first, second] ->
                        Result.map2
                            (,)
                            JD.decodeValue firstD first
                            JD.decodeValue secondD second
                            |> JD.Extra.fromResult

                    _ ->
                        JD.fail "There aren't two values in the list."
            )

Upvotes: 3

Related Questions