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".