#30daysofelm Day 16: Struggling slightly less with JSON
Kristian Pedersen
Posted on January 1, 2021
This is day 16 of my 30 day Elm challenge
Thanks to Wolfgang Schuster @wolfadex , for helping me out yesterday!
Also, thanks to Sephi at the Elm Slack for helping me out today!
Table of contents
- Table of contents
- 1. Simplifying Main.elm
- 2. Hardcoded JSON with one field
- 3. Decoding a person object with fields of different types (name, age)
- 4. Summary
1. Simplifying Main.elm
I couldn't figure out how to deal with JSON yesterday, so I thought it would be a good idea to step back a bit.
Yesterday, I simply copied my project from day 9, Astronomy data from Python in Elm.
This is a project I want to work more on, but its Main.elm
is nearly 100 lines of code, with subscriptions, cmd msgs, and other stuff needed to communicate with an external API.
Nothing crazy, but it's enough to get in my way when learning a new concept.
Here's a very minimal starting point that I think will serve me better today:
module Main exposing (main)
import Html exposing (div, text)
main =
div [] [ text "hi" ]
The first line gets added automatically by elm-format
. The standard model-update-view architecture with Browser.sandbox
is great, but I don't need it when learning the very basics of decoding JSON.
2. Hardcoded JSON with one field
Here's a very basic (and silly) example of decoding a JSON object with one field.
module Main exposing (main)
import Dict exposing (Dict)
import Html exposing (div, text)
import Json.Decode exposing (..)
name =
""" {"name": "Kristian"} """
getName =
field "name" string
main =
div []
[ text
(case decodeString getName name of
Ok result ->
result
Err e ->
e |> Debug.toString
)
]
The triple quotes are neat - they're usually used for multi-line strings, but by using them here, I don't have to put backslashes before every "
character.
There are two parts to decoding a JSON object. First, you describe what Elm should get, and then another function does it.
The main bit of code here is decodeString getName name
.
Its full name is JSON.Decode.decodeString, and it's what you want for converting anything to a string, I guess. :)
I think this approach without an external API would be a nicer introduction to JSON decoding, as several people have told me that they also struggled a bit to understand it to begin with.
3. Decoding a person object with fields of different types (name, age)
person : String
person =
"""
{
"person": {
"name": "Kristian",
"age": 31
}
}
"""
This is more complex, but it's also pretty easy to work with.
First, we describe the fields to Elm before doing anything:
howToGetName : Decoder String
howToGetName =
field "person" (field "name" string)
howToGetAge : Decoder Int
howToGetAge =
field "person" (field "age" int)
howToGetPerson : Decoder Person
howToGetPerson =
map2 Person
(field "name" string)
(field "age" int)
type alias Person =
{ name : String
, age : Int
}
When I wrote this, I just pasted the map2
line from the JSON guide. It doesn't reference the "person" field, so it doesn't work.
Failure "Expecting an OBJECT with a field named 'name'" <internals>
map2
is not the same as List.map
, but the way! Its real name is Json.Decode.map2
.
Let's compare their type signatures:
map : (a -> b) -> List a -> List b
map2 : (a -> b -> value) -> Decoder a -> Decoder b -> Decoder value
I guess map2
takes two values a
and b
and returns a value. Then it returns two decoders and turns them into one, or maybe I'm wrong.
Anyway, here's how they're handled. I went for a pretty generic solution, with a Debug.toString
that (I think) will convert anything to a string, even strings.
getInfo : String -> Decoder a -> String
getInfo sourceJSON decoder =
case decodeString decoder sourceJSON of
Ok result ->
result |> Debug.toString
Err e ->
e |> Debug.toString
main : Html msg
main =
div []
[ text (getInfo person howToGetName)
, text (getInfo person howToGetAge)
, text (getInfo person howToGetPerson)
]
When I do get the last line working, there's one thing I would like to improve. getInfo
converts the entire result to a string, but I might want to deal with the individual fields.
getPersonInfo : String -> Decoder Person -> String
getPersonInfo sourceJSON decoder =
case decodeString decoder sourceJSON of
Ok { name, age } ->
name ++ ", " ++ (age |> String.fromInt)
Err e ->
e |> Debug.toString
main : Html msg
main =
div []
[ text (getInfo person howToGetName)
, text (getInfo person howToGetAge)
, text (getPersonInfo person howToGetPerson)
]
Instead of saying Ok result
, I'm destructuring result
as Ok { name, age }
. It does compile, so I think I'm on to something.
4. Summary
That's all for today. I feel like I've taken some good steps towards being able to decode my astronomy JSON, and do some fun things with it.
I'm very happy I decided to start from the simplest possible code, rather than dealing with an external API and all the extra code that forces me to keep track of.
See you tomorrow!
Posted on January 1, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 28, 2024