nsadeh
1
I am using elm/json to decode results from a network call. I have this situation where I get JSON that looks like this:
{ "action_type": "InsertNewExercise",
"payload": {
"day": 1,
"order": 3,
"exercise": {
"name": "Deadlift",
"sets": [
{
"reps": 8,
"weight": 185
},
{
"reps": 8,
"weight": 185
}
]
}
}
}
The payload and action_type are linked, so I want to create a decoder which attempts to decode payload given some value of action_type:
decodePayload : String -> D.Decoder Action
decodePayload action =
case action of
"InsertNewExercise" ->
D.map Insert decodeInsert
_ ->
D.fail ("Failed to decode this action: " ++ action)
I used the JSON.andThen documentation to write this decoder:
payload: D.Decoder Action
payload = D.field "action_type" D.string
|> D.andThen decodePayload
But to actually use, I don’t have a way to not nest the JSON:
decode = D.map RowResult (D.field "payload" payload)
I obviously get the error that field action_type was not found inside the payload.
How do I get it to not have to nest?
nsadeh
2
EDIT: I made it work with a real ugly approach. Essentially I created an intermediate representation of the raw value and then worked over it:
-- types: notice the new "raw" type --
type Action = Action1 ActionBody | Action2 ActionBody
type alias Row = { action: Action }
type alias RawRow = { actionType: String, actionBody: D.Value }
rawDecoder: D.Decoder RawRow
rawDecoder = D.map2 RawRow
( D.field "action_type" D.string )
( D.field "payload" D.value )
rowWithAction: D.Decoder Action -> D.Decoder Row
rowWithAction decoder = D.field "payload" decoder
-- implementation as above --
decodePayload : String -> D.Decoder Action
-- this decoder works --
decoder: D.Decoder Row
decoder = D.andThen (\raw -> ( rowWithAction <| decodePayload row.actionType ) ) rawDecoder
This does work, but it’s ugly. Would love to hear of a more elegant solution!
All that is missing is to target the field payload
.
decoder : D.Decoder Insert
decoder =
D.field "action_type" D.string
|> D.andThen decodePayload
decodePayload action =
case action of
"InsertNewExercise" ->
D.field "payload" decodeInsert
_ ->
D.fail ("Failed to decode this action: " ++ action)
type alias Insert =
{ day : Int, order : Int }
decodeInsert =
D.map2 Insert
(D.field "day" D.int)
(D.field "order" D.int)
So
D.field "payload" decodeInsert
instead of D.map Insert decodeInsert
I made myself this little helper:
switch : String -> List ( String, Decoder a ) -> Decoder String -> Decoder a
switch notFoundMsg groups keyDecoder =
let
getDecoder key =
Dict.get key (groups |> Dict.fromList)
|> Maybe.withDefault (Decode.fail <| notFoundMsg ++ ": " ++ key)
in
Decode.andThen getDecoder keyDecoder
Then I would use it like this:
type Action = InsertNewExerciseAction NewExerciseData | SomeOtherAction
type alias NewExerciseData = {
-- Your data here
}
actionDecoder: String -> Decoder Action
actionDecoder action =
Decode.field "action_type" Decode.string
|> switch "this action type does not exist"
[ ( "InsertNewExercise", Decode.field "value" insertNewExerciseDecoder |> Decode.map InsertNewExerciseAction )
]
lydell
5
This is an “idiomatic” way of writing that:
actionDecoder : String -> Decoder Action
actionDecoder action =
Decode.field "action_type" Decode.string
|> Decode.andThen
(\actionType ->
case actionType of
"InsertNewExercise" ->
Decode.field "value" insertNewExerciseDecoder
|> Decode.map InsertNewExerciseAction
_ ->
Decode.fail ("this action type does not exist: " ++ actionType)
)
You’ll almost certainly encounter something like that if you work on somebody else’s code base at least.
1 Like
system
Closed
6
This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.