Reputation: 2034
I see
I don't believe the first is relevant for elm 0.19. I could not find a decode
function in NoRedInk/elm-json-decode-pipeline and I do not believe the :=
infix operator is still valid.
The second addresses the slightly different question of conditionally decoding based on the value of a JSON field.
If I have data coming over a port and the following types:
import Json.Decode exposing (Decoder, map, oneOf, string, succeed, andThen, map2, map4)
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 can I write a decoder for tagged union type Status
to decode the data coming back in from the port?
Best I got so far is something like
statusDecoder : Decoder Status
statusDecoder =
oneOf
[ dataDecoder andThen (\data -> succeed (Authenticated data)
, errorDecoder andThen (\error -> succeed (Unauthenticated (Just error)))
]
which isn't valid, or
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 = "" })
which is really ugly and doesn't feel right.
Upvotes: 2
Views: 2050
Reputation: 21037
you've done all the hard work. This should be all you need now. Note .map
is the quick way to do your case statement stuff.
import Json.Decode as Decode exposing (Decoder)
statusDecoder =
Decode.oneOf
[ Decode.map Authenticated dataDecoder
, Decode.map Unauthenticated <| Decode.maybe errorDecoder
]
Upvotes: 3
Reputation: 2034
The solution I went with is to use Json.Decode.map to translate the Decoder Data
and Decoder Error
types to Decoder Status
types using the Status
type constructors Authenticated data
and Unauthenticated Maybe error
.
I can then use the Json.Decode.oneOf function to decode into one or the other depending on the shape of the data because both decoders are now of the same type Decoder Status
.
dataDecoder : Decoder Status
dataDecoder =
map Authenticated <|
map4 Data
(field "accessToken" string)
(field "email" string)
(field "name" string)
(field "nickname" string)
errorDecoder : Decoder Status
errorDecoder =
map Unauthenticated <|
maybe <|
map2 Error
(field "error" string)
(field "errorDescription" string)
statusDecoder : Decoder Status
statusDecoder =
oneOf [ dataDecoder, errorDecoder ]
For brevity you can then use the Json.Decode.decodeValue function with the statusDecoder
function defined above to decode the Json.Decode.Value
coming out of the port in your update function.
-- UPDATE
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ReceiveLoginResponse response ->
let
decodedResponse = decodeValue Auth.statusDecoder response
in
case decodedResponse of
Ok status ->
( { model
| authStatus = status
}
, Cmd.none
)
...
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ loginResponse ReceiveLoginResponse
]
Upvotes: 2