How to decode JSON into a custom type (union type/adt)?

Given the following data types:

import Json.Decode exposing (Decoder, andThen, map, map2, map4, oneOf, string, succeed, field)


port loginResponse : (Value -> msg) -> Sub msg


type Status
    = Authenticated Data
    | Unauthenticated (Maybe Error)


type alias Data =
    { accessToken : String
    , email : String
    , name : String
    , nickname : String
    }

dataDecoder : Decoder Data
dataDecoder =
    map4 Data
        (field "accessToken" string)
        (field "email" string)
        (field "name" string)
        (field "nickname" string)

type alias Error =
    { error : String
    , errorDescription : String
    }

errorDecoder : Decoder Error
errorDecoder =
    map2 Error
        (field "error" string)
        (field "errorDescription" string)

How might I go about writing a decoder to decode Json.Decode.Value types coming out of a Port into the Status custom type?

Closest I can think of is something like:

getStatus : Json.Decode.Value -> Status
getStatus value =
    let
        decodedData =
            decodeValue dataDecoder value
    in
    case decodedData of
        Ok data ->
            Authenticated data

        Err _ ->
            let
                decodedError =
                    decodeValue errorDecoder value
            in
            case decodedError of
                Ok error ->
                    Unauthenticated (Just error)

                Err err ->
                    Unauthenticated (Just { error = "There was a problem decoding the JSON", errorDescription = "" })

but that doesn’t feel very elegant.

This package has some examples in the readme on how to write decoders for custom types using a do-notation style.

https://package.elm-lang.org/packages/webbhuset/elm-json-decode/latest/

Something like this should probably work in your case:

statusDecoder : Decoder Status
statusDecoder =
    oneOf
        [ dataDecoder
            |> map Authenticated
        , maybe errorDecoder
            |> map Unauthenticated
        ]

Usually you write a decoder for each branch of your custom type.

If the JSON data has a different shape for each case then you can use Decode.oneOf to choose which decoder to use as shown by @albertdahlin above.

If the JSON data has the same shape all the time, you will need to decide based on the value of one of the fields in the data itself (e.g. a type field or an authenticated boolean flag). This ends up looking roughly like typeFieldDecoder |> Decode.andThen chooseDecoderFromType.

For some more detailed examples of various scenarios decoding custom types, check out the following article. In particular, #4 (decoding based on JSON shape) and #5 (decoding based on a field value).

2 Likes

You’ve already received an answer on Stack Overflow.

Please don’t cross-post, at least not without explicitly saying so and following up with answers to make it easier for future readers to find them. We’re also not your servants and don’t like to be treated as if we were. You posted your own solution on SO yesterday, which is good, but here people are still trying to help you because you haven’t followed up here.

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.