jsonelmdecodercustom-type

How to decode a JSON array into Elm list of custom types


I want to decode a JSON array of payload objects into List Payload where each Payload is a custom type:

type Payload
     = PayloadP1 P1
     | PayloadP2 P2

After decoding PayloadP1 and PayloadP2 using decoders below how do I decode Payload?

type alias P1  = 
    { id : Int
    , st : String
    }

type alias P2  = 
    { id : Int
    , s1 : String
    , s2 : String
    }

type Payload
     = PayloadP1 P1
     | PayloadP2 P2

type alias PayloadQueue = List Payload

decodeP1 : Jd.Decoder P1
decodeP1 =
    Jd.map2 P1
        (Jd.field "id" Jd.int)
        (Jd.field "st" Jd.string)

decodeP2 : Jd.Decoder P2
decodeP2 =
    Jd.map3 P2
        (Jd.field "id" Jd.int)
        (Jd.field "p1" Jd.string)
        (Jd.field "p2" Jd.string)

decodePayload =
    Jd.field ".type" Jd.string
    |> Jd.andThen decodePayload_

{-
decodePayload_ : String -> Jd.Decoder Payload
decodePayload_ ptype =
    case ptype of
        "P1" -> decodeP1
        "P2" -> decodeP2
-}

json_str = """[
   {".type" : "P1", "id" : 1, "st" : "st"},
   {".type" : "P2", "id" : 2, "p1" : "p1", "p2" : "p2"},
]"""

Solution

  • You need to wrap P1 and P2 in PayloadP1 and PayloadP2 respectively in order to return a common type from each branch, which you can do using map. Then you also need to account for the possibility that the type field is neither P1 or P2. In that case you can either provide a default or return an error using fail. I've done the latter below.

    decodePayload_ : String -> Jd.Decoder Payload
    decodePayload_ ptype =
        case ptype of
            "P1" -> decodeP1 |> Jd.map PayloadP1
            "P2" -> decodeP2 |> Jd.map PayloadP2
            _ -> Jd.fail "invalid type"