Given the following JSON:
[
{
"id": 0,
"name": "Item 1",
"desc": "The first item"
},
{
"id": 1,
"name": "Item 2"
}
]
How do you decode that into the following Model:
type alias Model =
{ id : Int
, name : String
, desc : Maybe String
}
So if you're looking for a zero-dependency solution that doesn't require Json.Decode.Pipeline
.
import Json.Decode as Decode exposing (Decoder)
modelDecoder : Decoder Model
modelDecoder =
Decode.map3 Model
(Decode.field "id" Decode.int)
(Decode.field "name" Decode.string)
(Decode.maybe (Decode.field "desc" Decode.string))
If you want to do this using the Model
constructor as an applicative functor (because you'd need more 8 items).
import Json.Decode as Decode exposing (Decoder)
import Json.Decode.Extra as Decode
modelDecoder : Decoder Model
modelDecoder =
Decode.succeed Model
|> Decode.andMap (Decode.field "id" Decode.int)
|> Decode.andMap (Decode.field "name" Decode.string)
|> Decode.andMap (Decode.maybe (Decode.field "desc" Decode.string))
Both of which can be used with List
s with Decode.list modelDecoder
. I wish the applicative functions were in the standard library, but you'll have to reach into all of the *-extra libraries to get these features. Knowing how applicative functors work will help you understand more down the line, so I'd suggest reading about them. The Decode Pipeline solution abstracts this simple concept, but when you run into the need for Result.andMap
or any other of the andMap
s because there's not a mapN
for your module or a DSL, you'll know how to get to your solution.
Because of the applicative nature of decoders, all fields should be able to be processed asynchronously and in parallel with a small performance gain, instead of synchronously like andThen
, and this applies to every place that you use andMap
over andThen
. That said, when debugging switching to andThen
can give you a place to give yourself an usable error per field that can be changed to andMap
when you know everything works again.
Under the hood, JSON.Decode.Pipeline
uses Json.Decode.map2
(which is andMap
), so there's no performance difference, but uses a DSL that's negligibly more "friendly".
Brian Hicks has a series of posts on JSON decoders, you probably want to specifically look at Adding New Fields to Your JSON Decoder
(which handles the scenario where you may or may not receive a field from a JSON object).
To start with, you'll probably want to use the elm-decode-pipeline package. You can then use the optional
function to declare that your desc
field may not be there. As Brian points out in the article, you can use the maybe
decoder from the core Json.Decode
package, but it will produce Nothing
for any failure, not just being null
. There is a nullable
decoder, which you could also consider using, if you don't want to use the pipeline module.
Your decoder could look something like this:
modelDecoder : Decoder Model
modelDecoder =
decode Model
|> required "id" int
|> required "name" string
|> optional "desc" (Json.map Just string) Nothing
Here's a live example on Ellie.
Brian Hicks' "Adding New Fields to Your JSON Decoder" post helped me develop the following. For a working example, see Ellie
import Html exposing (..)
import Json.Decode as Decode exposing (Decoder)
import Json.Decode.Pipeline as JP
import String
type alias Item =
{ id : Int
, name : String
, desc : Maybe String
}
main =
Decode.decodeString (Decode.list itemDecoder) payload
|> toString
|> String.append "JSON "
|> text
itemDecoder : Decoder Item
itemDecoder =
JP.decode Item
|> JP.required "id" Decode.int
|> JP.required "name" Decode.string
|> JP.optional "desc" (Decode.map Just Decode.string) Nothing
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With