How JSON decoders work

JSON decoders are perhaps a huge stumbling block for beginners. They are extremely unintuitive for many including myself (at first). Anything beyond the one level map would totally trip me up. Let's see what they are.

The problem they solve

The main thing that JSON decoders solve is the fact that when Elm gets data from an external source that data can technically be anything. We usually want it to be in one format but there's nothing stopping JavaScript from just sending "💩" emojis.

Elm is statically typed, meaning all the types have to be known as compile time, not at runtime when JS pulls out the emoji keyboard at an inconvenient time. So what if we want {"id": 2} but JavaScript sends {"id": null }? That's not even close to what we want! It's null not an int! Normally in JavaScript we would just find out later when we try to use that in an HTTP request or something, in Elm we don't have runtime exceptions and we don't have null so we have to tell JS (or any external source) that we just won't stand for that behavior (in a nice way

Two black boxes

So I think most of the confusion for me is the fact that we have not just one but two types that you can't totally see into. One being a "decoder" one being

My "simple" JSON, and how to decode it

This is the process I went through to Decode this:

{"action" : Action} OR {"action": Action, "error": "I can't do this because..."}

This appears simple however it's quite tricky because I essentially have two different states that are differentiated only by "error"'s presense. This really isn't too crazy but it does make it a bit more difficult and extends past the safe boundaries of Json.Decode.Pipeline


λ ~/code/rapidash/ master* elm repl
---- elm-repl 0.18.0 -----------------------------------------------------------
 :help for help, :exit to exit, more at <https://github.com/elm-lang/elm-repl>
--------------------------------------------------------------------------------
> :help
General usage directions: <https://github.com/elm-lang/elm-repl#elm-repl>
Additional commands available from the prompt:

  :help			List available commands
  :flags		Manipulate flags sent to elm compiler
  :reset		Clears all previous imports
  :exit			Exits elm-repl

> import Json.Decode exposing (..)
> decodeString
<function> : Json.Decode.Decoder a -> String -> Result.Result String a
> ds = decodeString
<function> : Json.Decode.Decoder a -> String -> Result.Result String a
> ds (field "a" int) """{"a": 2}"""
Ok 2 : Result.Result String Int
> ds (field "a" string) """{"a": 2}"""
Err "Expecting a String at _.a but instead got: 2" : Result.Result String String
> ds (field "a" string) """{"a": "asdf"}"""
Ok "asdf" : Result.Result String String
> andThen
<function>
    : (a -> Json.Decode.Decoder b)
      -> Json.Decode.Decoder a -> Json.Decode.Decoder b
> andThen
<function>
    : (a -> Json.Decode.Decoder b)
      -> Json.Decode.Decoder a -> Json.Decode.Decoder b
> andThen (\ str -> suceed str)
-- NAMING ERROR ---------------------------------------------- repl-temp-000.elm

Cannot find variable `suceed`

4|   andThen (\ str -> suceed str)
                       ==^^==
Maybe you want one of the following?

    succeed
    Json.Decode.succeed


> andThen (\ str -> succeed str)
<function> : Json.Decode.Decoder a -> Json.Decode.Decoder a
> andThen (\ str -> if str == "foo" then succeed str else fail "was not foo")
<function> : Json.Decode.Decoder String -> Json.Decode.Decoder String
> actionDecoder = andThen (\ str -> if str == "foo" then succeed str else fail "was not foo")
<function> : Json.Decode.Decoder String -> Json.Decode.Decoder String
> andThen (\ str -> if str == "foo" then succeed str else fail "was not foo")
<function> : Json.Decode.Decoder String -> Json.Decode.Decoder String
> actionDecoder = andThen (\ str -> if str == "foo" then succeed str else fail "was not foo") Decode.string
-- NAMING ERROR ---------------------------------------------- repl-temp-000.elm

Cannot find variable `Decode.string`.

3| actionDecoder = andThen (\ str -> if str == "foo" then succeed str else fail "was not foo") Decode.string
                                                                                               ==^^====^^==^
No module called `Decode` has been imported.


> actionDecoder = andThen (\ str -> if str == "foo" then succeed str else fail "was not foo") string
<decoder> : Json.Decode.Decoder String
> type Action = Foo | Bar
> actionDecoder = andThen (\ str -> if str == "foo" then succeed Foo else fail "was not foo") string
<decoder> : Json.Decode.Decoder Repl.Action
> ds (field "a" actionDecoder) """{"a": "asdf"}"""
Err "I ran into a `fail` decoder at _.a: was not foo"
    : Result.Result String Repl.Action
> ds (field "a" actionDecoder) """{"a": "foo"}"""
Ok Foo : Result.Result String Repl.Action