JSON decoding in Elm, explained step by step

An Elm beginner stunned by the decoders syntax, not realizing that it may be more familiar than they think. Hello, fellow Elm developer! For Elm beginners, JSON decoding is often one of the steepest points to overcome in your learning journey, especially when Elm is your first functional programming language. With this article, I hope to […]

An Elm beginner stunned by the decoders syntax, not realizing that it may be more familiar than they think.

Hello, fellow Elm developer!

For Elm beginners, JSON decoding is often one of the steepest points to overcome in your learning journey, especially when Elm is your first functional programming language.

With this article, I hope to give you some easy first steps (what is a decoder, how to use one) and more advanced explanations for real world scenarios (composition, refactoring).

It looks hard at first, but trust me, it gets easier with practice.

It’s exactly like LEGO blocks, excepted that you can change the plots and holes shapes, which in fact makes it not like LEGO blocks at all.

It’s more like having sex: you try to put the fillydiddly in the holydeedoo, make it work and when you finally made it, you light up a cigarette and think about your life.

0. First of all, what is a decoder?

To use data contained in a JSON string (fetched from a server, most of the time) as Elm variables and benefit from their enforced type safety, you can’t just directly access the said JSON data like a basic object as you could do in JavaScript.

You have to write a prediction of future data structure and types, thus allowing you to handle all possibilities and generate the right Elm value with the right structure at the end.

It looks a bit like JSON schemas blended with redux selectors!

To describe your data structure and give each value a type, you will use functions called decoders. They are generally built using primitive decoders provided by Elm, and describe how to process your JSON data to get values it contains.

The main decoders’ feature is to always return another decoder when nested together. The same way you could nest different data types in JSON, you nest different decoders in Elm to get that data and exploit it as variables.

It means you could describe an almost infinite JSON nesting with conditions for potentially absent or empty values everywhere and still keep a perfect type safety, with almost the same basic understanding as the one required to build the simplest decoders.

You’ll learn how decoders gears work once, then any weird payload will be exploitable in a similar manner! o/

1. JSON strings

You need an actual JSON string to decode. In Elm you can use things like:

  • data sent by an API, of course
  • a regular string with escaped characters (looks ugly): "{"hey":"bro"}"
  • a multiline string with 3 pairs of double quotes around it (more readable and no escaping required): """{"hey":"sis"}"""
A young JS developer working with raw JSON data in Elm. You can see the struggle in his eyes. “wtf should I do with this sh** guys? plz send help”

2. Primitive decoders

Here is the simplest decoder example: Json.Decode.value

The primitive decoders are:

  • booleans: Json.Decode.bool
  • strings: Json.Decode.string
  • integers: Json.Decode.int
  • floating-point numbers: Json.Decode.float

These decoders are the basic building blocks of almost all other decoders.

There are many more decoders in Json.Decode, and we will see some of them later.

Attention! don't get the same confusion as many did: There is also a function called Json.Decode.decodeStringwhich is not the same as Json.Decode.string.

  • string is the primitive decoder used to translate JSON string values to Elm variables.
  • decodeString takes a decoder like string, bool, or your own decoders, and a JSON string as its two parameters and decodes the given JSON string with the provided decoder.

3. Time to Decode!

Finally, you need a function taking your decoder as parameter to run it against your JSON data: yourInitialFunction yourDecoder yourJsonData.

This initial function could be an Http.get for example, or simply Json.Decode.decodeString.

As we saw just a bit earlier, it's used to run a decoder that you provide on a given string.

Example code usable in REPL ($ elm repl):

https://medium.com/media/522d76ad289c189d24503a54c981e207/href

Pretty easy, right?

If you wonder what is this Ok thing in the returned value, just remember it could be an Err instead. It is used to dissociate successful and failed decoding attempts.

4. JSON objects and key/value fields

Ok, so now we want to do the same with a JSON object field:

https://medium.com/media/683023b5aa59e42c0533e715120631f3/href

It’s pretty simple, too, you just need to use a field decoder, give the field key, in this case "elm", and a decoder matching the value's type: string.

https://medium.com/media/631b3d07bc21cf82c1595ce788e20bc4/href

Full code:

https://medium.com/media/005552d58a108eb82652a15174bc6a96/href

5. Nested values

There are multiple ways to get a nested value. Let’s say we have this nested object:

https://medium.com/media/97532476a3035cb6c3e37955f65f8664/href

The simplest way to get to this data is by using Json.Decode.at, and tell it to decode a string, going 2 nesting levels deep, through one then two keys:

https://medium.com/media/a0333fe1f4c0e8db9500d56ec7e0465f/href

Full code:

https://medium.com/media/9382973012f1e6f5595b3e3f40788a91/href

An Elm developer picking stuff in all that finally untangled data.

6. Decoders composition

All of this is nice, but what if you want multiple values? In a real world scenario, you will probably decode a ton of complex values at once! That’s where you will benefit from Elm decoders’ design.

Understand the principles

As you probably noticed in the previous examples, we can use decoders in other decoders. In (field "elm" string), we pass the string decoder as a parameter to the field decoder, and then we pass the resulting function as a decoder too to decodeString.

To decode nested values, you nest decoders! Simple, right? Remember how we used (at ["one", "two"] string) to get a nested value? at is in fact a shorthand function for (field "one" (field "two" string)).

This is really important to understand: the at decoder simplifies other decoders usage, and is itself a decoder! And guess what? You can compose your own very complex decoders in the exact same way!

For example, Json.Decode.list takes another decoder as parameter, to specify which type of data the list will contain.

Examples

Let’s take an example JSON with multiple nested values.

https://medium.com/media/9a4763d5447bd40e39bb4e0e047bc649/href

What should we do if we wanted to get a record looking like this:

https://medium.com/media/16bf9c2fc4c7487dab69b3df413eb2e7/href

  • we could use a let … in … pattern to first decode individually each value we want, then create our record with it:

https://medium.com/media/b9cabe6636b105baecb496f0f42f3e88/href

Easy, but unusable with big data trees. Are there any other options? Of course!

  • We could provide a type alias to another decoder (like Json.Decode.map) that will use this type definition to build our result.

Here, we have 3 values to map to our record, and Json.Decode.map3 was exactly made for that purpose:

https://medium.com/media/241a3c6ff9e8936073f7d8d8106d8853/href

This is possible because type aliases can be used like constructor functions. Yes, that means you could use a function instead!

https://medium.com/media/ad32a2ade4e3af2e53b8f4aae3fd68eb/href

As you can see, there are multiple ways of decoding your data, especially when the said data is short and simple. As we will see in another post, advanced use of decoders is necessary to deal with really big trees and edge cases.

7. What about optional data?

Now that you know how to get a specific bit of data, you may encounter the case where an API might send empty values or missing fields (sadly, inconsistent APIs are still a thing). If you’re not sure that your JSON field exists or has a value assigned to it, you can use a maybe decoder.

  • if your field will exist but may have no value:

https://medium.com/media/d60e72dd89d865b03bf52c8a922b0bb0/href

  • if your field itself can be missing:

https://medium.com/media/657b5b999360f40590263713f91d059b/href

  • of course, you could use both at the same time, if your field and its value could be missing!

8. So, what’s next?

You should now have a better grasp of how decoders work in Elm, and be able to create your own decoders with ease.

But to fully master decoders, it might be a good idea to read the Json.Decode docs and play a bit with advanced decoders like andThen or lazy.

If you’re still wondering why Elm doesn’t automagically generate your decoders’ code for you, here are some interesting insights on this topic by Elm author Evan Czaplicki.

You could also take a look at some interesting decoder packages like:

Thanks for reading!

First draft published on crucial.codes.


JSON decoding in Elm, explained step by step was originally published in JobTeaser Tech on Medium, where people are continuing the conversation by highlighting and responding to this story.

Source: JobTeaser