Search code examples
jsonfunctional-programmingdecodeelm

Elm JSON decoder for Union type with data


My json looks like this:

{"name": "providerWithVal", "value": "example"}

or like this:

{"name": "provider2"}

or

{"name": "provider3"}

My Elm union type is defined like so:

type Provider
    = ProviderWithVal String
    | Provider2
    | Provider3

I can write a decoder for a union type without data attached. But ProviderWithVal takes a string and I'm not sure how to make it all work.

This is what I have so far:

import Json.Decode as D

providerDecoder : D.Decoder Provider
providerDecoder =
    D.field "name" D.string |> D.andThen providerNameDecoder

providerNameDecoder : String -> D.Decoder Provider
providerNameDecoder string =
    case string of
        "providerWithVal" -> D.succeed ProviderWithVal
        "provider2" -> D.succeed Provider2
        "provider3" -> D.succeed Provider3
        _ -> D.fail <| "Invalid provider: " ++ string

Solution

  • The quick solution to your question is to replace D.succeed ProviderWithVal with D.map ProviderWithVal (D.field "value" Decode.string)

    But I would create a helper to match the target strings, and then use that in the following way:

    decoder =
        Decode.oneOf [ decodeWithVal, decodeP2, decodeP3 ]
    
    
    decodeWithVal =
        exactMatch (Decode.field "name" Decode.string)
            "providerWithVal"
            (Decode.map ProviderWithVal <| Decode.field "value" Decode.string)
    
    
    decodeP2 =
        exactMatch (Decode.field "name" Decode.string) "provider2" (Decode.succeed Provider2)
    
    
    decodeP3 =
        exactMatch (Decode.field "name" Decode.string) "provider3" (Decode.succeed Provider3)
    
    
    exactMatch : Decoder String -> String -> Decoder a -> Decoder a
    exactMatch matchDecoder match dec =
        matchDecoder
            |> Decode.andThen
                (\str ->
                    if str == match then
                        dec
    
                    else
                        Decode.fail <| "[exactMatch] tgt: " ++ match ++ " /= " ++ str
                )