# How to decode json based on previous value?

**URL:** <https://discourse.elm-lang.org/t/how-to-decode-json-based-on-previous-value/8222>\
**Category:** Learn\
**Created:** [March 6, 2022, 9:09pm UTC](https://discourse.elm-lang.org/t/how-to-decode-json-based-on-previous-value/8222 "2022-03-06T21:09:21Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![nsadeh](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/nsadeh/32/4444_2.png) [@nsadeh](https://discourse.elm-lang.org/u/nsadeh)\
**Post date:** [March 6, 2022, 9:09pm UTC](https://discourse.elm-lang.org/t/how-to-decode-json-based-on-previous-value/8222/1 "2022-03-06T21:09:21Z")

</div>

I am using elm/json to decode results from a network call. I have this situation where I get JSON that looks like this:

```auto
{ "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:

```auto
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:

```auto
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:

```auto
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?

---

<div class="post-metadata">

**Author:** ![nsadeh](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/nsadeh/32/4444_2.png) [@nsadeh](https://discourse.elm-lang.org/u/nsadeh)\
**Post date:** [March 6, 2022, 9:32pm UTC](https://discourse.elm-lang.org/t/how-to-decode-json-based-on-previous-value/8222/2 "2022-03-06T21:32:40Z")

</div>

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:

```auto
-- 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!

---

<div class="post-metadata">

**Author:** ![Sebastian](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/sebastian/32/98_2.png) [@Sebastian](https://discourse.elm-lang.org/u/Sebastian)\
**Post date:** [March 6, 2022, 9:52pm UTC](https://discourse.elm-lang.org/t/how-to-decode-json-based-on-previous-value/8222/3 "2022-03-06T21:52:18Z")

</div>

All that is missing is to target the field `payload`.

```auto
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`

---

<div class="post-metadata">

**Author:** ![sdeframond](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/sdeframond/32/4070_2.png) [@sdeframond](https://discourse.elm-lang.org/u/sdeframond)\
**Post date:** [March 8, 2022, 9:26am UTC](https://discourse.elm-lang.org/t/how-to-decode-json-based-on-previous-value/8222/4 "2022-03-08T09:26:44Z")

</div>

I made myself this little helper:

```auto
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:

```auto
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 )
            ]

```

---

<div class="post-metadata">

**Author:** ![lydell](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/lydell/32/178_2.png) [@lydell](https://discourse.elm-lang.org/u/lydell)\
**Post date:** [March 8, 2022, 4:44pm UTC](https://discourse.elm-lang.org/t/how-to-decode-json-based-on-previous-value/8222/5 "2022-03-08T16:44:07Z")

</div>

This is an “idiomatic” way of writing that:

```elm
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.

---

<div class="post-metadata">

**Author:** ![system](https://canada1.discourse-cdn.com/flex035/uploads/elm_lang/original/1X/50a05e53677a2c3b47776d7abd0f113eb50193a1.png) [@system](https://discourse.elm-lang.org/u/system)\
**Post date:** [March 18, 2022, 4:45pm UTC](https://discourse.elm-lang.org/t/how-to-decode-json-based-on-previous-value/8222/6 "2022-03-18T16:45:03Z")

</div>

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