In which I get angry because some aspect of Elm seems weird to me, and the docs aren't helping, so I jot down these notes because writing forces me to think deeply about things in a way that I'm incapable of doing otherwise gaaasspp
Preliminary: We'll be dumping Json.Decode
into our namespace to reduce typing, such that we have naked functions such as int
, which is actually Json.Decode.int
, and so forth:
import Json.Decode exposing (..)
Okay so first things first: a decoder is not a parser, but a way to take already-parsed JavaScript values like objects and numbers, and bring them into the statically-typed world of Elm, while accounting for all the crazy things that could happen en-route, like the value being null, or an object instead of a string, etc.
Often those JavaScript values originated from a JSON.parse()
operation, but not always.
For example, in event handling, when reading event objects (ev.target
, ev.clientX
, etc.) you'd use a JSON decoder, even though ev
object never existed as a JSON string.
To actually do JSON-parsing, you pass a decoder to the decodeString
function.
decodeString : Decoder -> String -> Result
Thus, you'll see something like this:
case decodeString int "42" of
Ok n ->
-- do something with n
Error message ->
-- something bad happened
decodeString
isn't very interesting or complicated.
From here on out, it becomes a question of building decoders, which as mentioned are descriptions of how to bring JavaScript values into your Elm program.
Firstly, there's a set of ready-to-use primitive decoders, one for each Elm primitive type.
Also, importantly, these are Elm primitive types, not JS primitive types.
These include:
Json.Decode.int
Json.Decode.string
Json.Decode.bool
-- etc
Next, Elm docs talk about how simple decoders "snap together" to form more complex decoders.
Namely, decoders for non-primitive types can't be used directly; they must first be parameterized by other decoders.
For example list
doesn't give us a decoder that's ready to use.
We must first parameterize it, as in list string
, which gives us a decoder that converts values into List String
.
list string -- produces Result (List String)
list bool -- produces Result (List Bool)
list (list string) -- produces Result (List (List String))
-- and so forth
But what's this Result
thing? Obviously, parsing JSON and converting arbitrary JavaScript values into Elm offers no guarantee of success.
First of all the JSON string might be malformed, in which case decodeString
fails.
Or, it produces an unexpected JavaScript value, in which case the decoder fails.
This is why these operations return a Result
rather than returning a value directly.
Thus, in order to compile, your program must handle both the Ok
and Error
cases of the Result
type.
Furthermore, this is why you'll sometimes see Json.succeed
, which is a sort of imposter decoder that ignores the JavaScript value handed to it and always just returns Ok
on pre-specified value.
It can then be passed in wherever a decoder is expected.
Building and using JSON decoders can get arbitrarily complicated, but this should provide enough of a conceptual foundation that the official docs can take over from here.