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 […]
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.
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/
You need an actual JSON string to decode. In Elm you can use things like:
Here is the simplest decoder example: Json.Decode.value
The primitive decoders are:
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.
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):
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.
Ok, so now we want to do the same with a JSON object field:
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.
There are multiple ways to get a nested value. Let’s say we have this nested object:
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:
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.
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.
Let’s take an example JSON with multiple nested values.
What should we do if we wanted to get a record looking like this:
Easy, but unusable with big data trees. Are there any other options? Of course!
Here, we have 3 values to map to our record, and Json.Decode.map3 was exactly made for that purpose:
This is possible because type aliases can be used like constructor functions. Yes, that means you could use a function instead!
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.
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.
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.