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
- Elm
- #tutorials