DEV Community

loading...

#30daysofelm Day 17: I decoded some JSON!

kristianpedersen profile image Kristian Pedersen ・2 min read

This is day 17 of my 30 day Elm challenge

About today's project

Code/demo: https://ellie-app.com/bYbhrKjmhJCa1

Screenshot of text showing planet distances

In today's project, I was finally able to decode some (hardcoded) JSON!

Bukkfrig's comment on yesterday's post saved the day, so thanks!

I'm happy with today's progress, and have other plans for the evening, so I'll leave the Python backend integration for later.

Some of the code here could be more elegant, but I'll save that for another day.

1. Describing the data

An individual planet in my JSON looks like this:

"Jupiter":{
        "lightMinutes":49.561547588282494,
        "xyz":[-15.160997437594864,35.36776769042324,86.01557267383876]
    }
Enter fullscreen mode Exit fullscreen mode

As you can see, the data and descriptions match:

type alias Planet =
    { lightMinutes : Float, xyz : List Float }

planetDecoder : String -> Decoder Planet
planetDecoder planetName =
    field planetName
        (map2 Planet
            (field "lightMinutes" float)
            (field "xyz" (list float))
        )
Enter fullscreen mode Exit fullscreen mode

2. Presenting the data (or the error)

planetDiv : String -> Planet -> Html msg
planetDiv p { lightMinutes, xyz } =
    div []
        [ h1 [] [ text p ]
        , text ((lightMinutes |> round |> String.fromInt) ++ " light minutes away")
        , br [] []
        , div [] <| (xyz |> List.map (\n -> text ((n |> String.fromFloat) ++ ", ")))
        ]


showPlanetOrError : String -> Html msg
showPlanetOrError planetName =
    case
        decodeString (planetDecoder planetName) hardcodedData
    of
        Ok planet ->
            planetDiv planetName planet

        Err err ->
            pre []
                [ text "Oops: "
                , br [] []
                , text ("    " ++ Debug.toString err)
                ]
Enter fullscreen mode Exit fullscreen mode

planetDiv is a pretty basic function that just returns an Html msg.

showPlanetOrError either shows the planetDiv, or logs out the error message.

3. Showing all planets

This kind of hardcoded solution takes me back to when I first learned JavaScript. Oh man. :D

main : Html msg
main =
    div [] <|
        List.map (\planet -> showPlanetOrError planet)
            [ "Sun"
            , "Mercury"
            , "Venus"
            , "Mars"
            , "Jupiter"
            , "Saturn"
            , "Uranus"
            , "Neptune"
            , "Pluto"
            ]
Enter fullscreen mode Exit fullscreen mode

4. Summary

JSON in Elm is tricky, and I'll probably make a few more mistakes, but the community is really helpful.

Thanks to everyone for helping a stranger in need! :)

Discussion (4)

pic
Editor guide
Collapse
bukkfrig profile image
bukkfrig

You still have this wierd function showPlanetOrError which does Json.Decode stuff and Html stuff.

The problem comes from the shape of your JSON. You are using the name of a field to represent what is actually data. You end up having to instruct the machine on how to handle a "Venus" field and a "Mars" field, etc. It might be human-readable, but it's not very machine readable.

Is it within your power to reshape the JSON sent by your API, so it's just a list of planets, and the planets have a name field?

In this Ellie (ellie-app.com/bYpVTc2KhSta1), there are two different versions of the same thing:

  • The first separates the decoding from the rendering, which separates the concerns, but it's very painful to do.
  • The second operates on a different shape of JSON, where it's just a list of planets and the planets have a name field. Now it's very simple!
Collapse
kristianpedersen profile image
Kristian Pedersen Author

Yeah, the original JSON is structured poorly. At first I did want to change it the way you describe, but I decided to keep it.

My thinking was that I might have to deal with other poorly structured JSON APIs in the future, but as you say, it has only made things more difficult.

Your second example is really nice to read - it's this kind of code I want to write. I would have gotten there sooner if I hadn't been so stubborn about the JSON shape, so that's a good lesson.

Also, I gotta say your first example is spectacular - the duplication at line 29 and 30 (List.map2 viewUnnamedPlanet) the nesting at line 78 (bodiesDecoder) are pure poetry. :D

Collapse
kodefant profile image
Lars Lillo Ulvestad • Edited

Good work!

JSON decoding/encoding is one of the trickier parts of Elm. I rarely make it all the way without looking at the docs. But it becomes easier every time you do it.

It's one of those things that gives your app a rock solid foundation and prevents stupid runtime exceptions. It's part of what could make you considerably happier than the average JS/TS developer in the long run 😄

Collapse
wolfadex profile image
Wolfgang Schuster

Happy to see that you're getting into JSON decoding a little more! I also struggled with it initially, but really love it now. I think for me, understanding JSON decoding is what got me to finally understand Elm and Haskell.

Can't wait for Day 18!