gimbar
gimbar

Reputation: 135

How do I decode custom types from two json fields?

I'm building an application with postgrest (backend) and elm (frontend) and right now I'm stuck writing a decoder. I don't quite understand how to decode to my specific type instead of the base type decoders like int and string.

When I take a look at how string is implemented (line 73 to 75) it's just a call to Elm.Kernel.Json.decodeString which in turn is in the js base of elm.

My JSON looks something like this:

{ "id" : 1
, "parent_id" : 1
, "body" : "text of my body"
}

OR

{ "id" : 1
, "parent_id" : 1
, "sub_parent_id" : 2
}

Is it possible to decode something like that into a single record type (Step) which contains a custom type with multiple constructors to match the two different fields (sub_parent_id and body) My decoder looks like this but doesn't compile:

import Api.Parent.Step.Types exposing ( Step, StepContent )
import Json.Decode exposing (..)
import Json.Decode.Pipeline exposing (..)

decoder : Decoder Step
decoder = 
    succeed Step
        |> required "id" int
        |> oneOf
            [ field "stepContent" stepBodyDecoder
            , field "stepContent" subStepDecoder
            ]

stepBodyDecoder : Decoder StepContent
stepBodyDecoder = 
    succeed StepContent
        |> required "body" string

subStepDecoder : Decoder StepContent
subStepDecoder =
    succeed StepContent
        |> required "sub_parent_id" decoder

Any my Types:

module Api.Parent.Step.Types exposing ( Step, StepContent )

type StepContent 
    = StepBody String
    | SubStep Step

type alias Step =
    { id : Int
    , stepContent : StepContent
    }

Upvotes: 1

Views: 541

Answers (2)

Titou
Titou

Reputation: 1008

The solution is written in the types (as often)

oneOf : List (Decoder a) -> Decoder a

So we have to supply a list of Decoder StepContent, which are the decoder functions.

And as per the relevant comment, to build a StepContent value, we need to form either constructor : a StepBody or a SubStep.

import Api.Parent.Step.Types exposing ( Step, StepContent )
import Json.Decode exposing (..)
import Json.Decode.Pipeline exposing (..)

decoder : Decoder Step
decoder = 
    succeed Step
        |> required "id" int
        |> required "stepContent"  (oneOf [ stepBodyDecoder, subStepDecoder ] )


stepBodyDecoder : Decoder StepContent
stepBodyDecoder = 
    succeed StepBody
        |> required "body" string

subStepDecoder : Decoder StepContent
subStepDecoder =
    succeed SubStep
        |> required "sub_parent_id" decoder

Upvotes: 0

glennsl
glennsl

Reputation: 29146

JSON decode pipelines expects succeed to be passed a function, and StepContent is not a function, but a type. Variant constructors are functions however, and if you look at the compiler error it suggests the right fix (although that's somewhat coincidental since it just suggest based on similar names):

I cannot find a `StepContent` variant:

28|     succeed StepContent
                ^^^^^^^^^^^
These names seem close though:

    StepBody
    Step
    OneOf
    SubStep

StepBody and SubStep are the ones you should use instead. stepBodyDecoder will work with just that change, and will get you at least one step further with subStepDecoder, but the type and decoder otherwise don't match the JSON. sub_parent_id is a number, not an object, so it seems like SubStep should take an Int instead of a Step. You can then possibly construct a separate hierarchical data structure in a subsequent step.

Upvotes: 1

Related Questions