spotifyovercastrssapple-podcasts

JSON Decoders

We discuss the basics of JSON Decoders, benefits compared to JSON in JavaScript, best practices, and how to get started learning.
April 27, 2020
#4

Basics

Ports and Flags

Here's an Ellie example that shows the error when you have an implicit type that your flags decode to and it's incorrect.

Sorry Dillon... Jeroen won the trivia challenge this time 😉 It turns out that ports will throw exceptions when they are given a bad value from JavaScript, but it doesn't bring down your Elm app. Elm continues to run with an unhandled exception. Here's an Ellie example.

Flags and ports will never throw a runtime exception in your Elm app if you always use Json.Decode.Values and Json.Encode.Values for them and handle unexpected cases. Flags and ports are the one place that Elm lets you make unsafe assumptions about JSON data.

Benefits of Elm's Approach to JSON

  • Bad data won't end up deep in your application logic where it's hard to debug (and discover in the first place)
  • Decouples serialization format from your Elm data types
  • You can do data transformations locally as you build up your decoder, rather than passing your giant data structure through a single transform function

Decoding Into Ideal Types

Note about Decode.maybe. It can be unsafe to use this function because it can cover up failures. Json.Decode.maybe will cover up some cases that you may not have intended to. For example, if an API returns a float we would suddenly get Nothing back, but we probably want a decoding failure here:

import Json.Decode as Decode """ {"temperatureInF": 86} """ |> Decode.decodeString (Decode.maybe (Decode.field "temperatureInF" Decode.int)) --> Ok (Just 86) """ {"temperatureInF": 86.14} """ |> Decode.decodeString (Decode.maybe (Decode.field "temperatureInF" Decode.int)) --> Ok Nothing

Json.Decode.Extra.optionalNullableField might have more intuitive and desirable behavior for these cases.

Thank you to lydell for the tip! See the discussion in this discourse thread.

Learning resource

Joël Quenneville’s Blog Posts About JSON Decoders

Guaranteeing that json is valid before runtime

Autogenerating json decoders

Organizing your decoders

Getting Started

  • Understand Json.Decode.map
  • Understand record type aliases - the function that comes from defining type alias Album = { ... }

Submit your question to Elm Radio!

Transcript

[00:00:00]
Hello, Jeroen.
[00:00:01]
How's it going?
[00:00:02]
How are you doing today?
[00:00:03]
I'm doing pretty well.
[00:00:04]
Hold up inside, but doing pretty well.
[00:00:05]
Yep, same here.
[00:00:06]
Well, why don't we keep ourselves occupied by talking about some Elm?
[00:00:07]
What are we talking about today?
[00:00:08]
Oh, Elm.
[00:00:09]
I like that.
[00:00:10]
We're going to talk about JSON decoders today.
[00:00:11]
Ah, that's a good one.
[00:00:12]
Yeah, JSON decoders.
[00:00:13]
So I feel like we're talking about JSON decoders, but this is really like a broader pattern.
[00:00:18]
So it's extra important to talk about.
[00:00:31]
Yeah, we should probably talk about what JSON decoders are and what they are for.
[00:00:38]
Yeah, maybe like imagine that somebody is like brand new to Elm even like what the heck
[00:00:43]
is a JSON decoder?
[00:00:45]
First of all, welcome.
[00:00:49]
So Elm gives you a lot of guarantees about how it will work.
[00:00:53]
It will produce no runtime errors and all the functions will get the types they expect
[00:01:00]
and the arguments they expect.
[00:01:02]
And that is very good.
[00:01:03]
That's something that we love about Elm.
[00:01:05]
But what happens when you get data from sources that you do not control?
[00:01:10]
Data from outside the Elm code.
[00:01:13]
So for instance, data that comes from HTTP or data that comes from ports or from HTML
[00:01:19]
events.
[00:01:20]
Those are pieces of data that Elm cannot control and cannot control the types of it.
[00:01:25]
So what you do is you validate the JSON that is coming in, if it's JSON, but we're going
[00:01:31]
to talk about JSON decoders.
[00:01:33]
You validate the fields that it has and the types that it has.
[00:01:36]
So if they match, you will get a successful decoding result of the type that you provided.
[00:01:42]
And otherwise you will get an error saying, hey, this field was not what I expected or
[00:01:47]
this field was missing.
[00:01:49]
So from that point on, you do have the data that you want to work with.
[00:01:52]
So if you wanted a record with this and that field with these types, then now you have
[00:01:57]
them.
[00:01:59]
You have successfully told Elm, this is data that I'm going to work with and the data that
[00:02:04]
you're going to have to play with from here on.
[00:02:07]
So that is basically the idea about JSON decoders from what I understand them.
[00:02:11]
Yes, and now, okay, so this is interesting because I'm realizing that from the description
[00:02:17]
you gave, this is basically the more specific instance of the general concept that we described
[00:02:24]
in our opaque types episode.
[00:02:26]
Because in our opaque types episode, we described this sort of ability to do runtime type checking
[00:02:33]
by conditionally returning a type when a validation passes, right?
[00:02:38]
And that's exactly what you just described.
[00:02:41]
And it turns out that JSON decoders are just a special case of that pattern.
[00:02:46]
And the sort of opaque type patterns we talked about are a more general tool that you can
[00:02:52]
use to build that sort of thing yourself.
[00:02:54]
Yeah, that's a good point.
[00:02:56]
So one other point I wanted to make is that just to contrast this against what you would
[00:03:03]
be doing in other languages, let's talk about what that would look like doing this in JavaScript,
[00:03:07]
right?
[00:03:08]
So you would say, you know, you have some JSON response from an HTTP response.
[00:03:15]
And you say that it's JSON, so it's decoded as JSON.
[00:03:19]
And then you do, you know, response.data.person.name.
[00:03:27]
And then maybe it's undefined because you're reaching in and grabbing an object key for
[00:03:34]
which there's no value.
[00:03:36]
Or maybe something's blowing up because you're dereferencing something that was that was
[00:03:40]
not there.
[00:03:41]
Or, you know, so basically, you say that you that it's possible to have a crash there or
[00:03:46]
undefined behavior.
[00:03:47]
Yeah, it's possible to have a crash or worse.
[00:03:51]
The worst case is that you don't have a crash, and you have some bad or unexpected thing
[00:03:58]
happen.
[00:03:59]
Maybe it's a crash, maybe it's a weird bug.
[00:04:02]
It's in some ways even harder to debug what's going on if it's not a crash, if it's just
[00:04:08]
weird behavior, if it's just something that some undefined value gets passed into someplace,
[00:04:13]
it gets concatenated with some string, which makes it the empty string, and then that goes
[00:04:17]
in some other place.
[00:04:19]
And then it tries to look that up as the key for some other HTTP request.
[00:04:25]
And then the HTTP request 404s and you're like, what?
[00:04:29]
Yeah, so basically, you will take much longer to find the problem that there is a problem.
[00:04:36]
Exactly.
[00:04:37]
Yeah.
[00:04:38]
So it's lengthening that feedback loop where you're taking longer to discover what's going
[00:04:41]
on.
[00:04:42]
And, you know, if you just think about it from having confidence in your code point
[00:04:46]
of view, how can you be confident that your code is, you know, it's one type of confidence
[00:04:53]
about your code that you even just grabbed data that actually exists and that like, I
[00:04:58]
don't know, if you have an API and you're saying, hmm, this would be kind of nice if
[00:05:02]
we renamed this because we're calling it this thing, but we've kind of discovered some things
[00:05:08]
about it and we think this would be a better name for this field.
[00:05:11]
How confident are you going to be doing that if you're grabbing that data from JavaScript?
[00:05:16]
Not very much.
[00:05:18]
You're probably just not going to bother doing it, right?
[00:05:22]
If the final replace is okay, if there are not too many instances, then let's try it.
[00:05:29]
Maybe try it, but then you're going to try it and then what are you going to do?
[00:05:33]
You're going to go through the app.
[00:05:34]
You're not just going to run the part that makes the HTTP request, but you're going to
[00:05:39]
run every part that you think uses that payload from that HTTP request to run through every
[00:05:45]
single code path with every single permutation of all the possible conditions to make sure
[00:05:50]
you exercise all possible code.
[00:05:52]
Sounds like fun.
[00:05:53]
Does that sound fun to you?
[00:05:56]
Yeah.
[00:05:57]
How else do you want to pass your afternoon?
[00:06:01]
This is maybe a good thing if you're new to Elm and listening to this and you're like,
[00:06:07]
JSON decoders, why can't I just use my data?
[00:06:11]
That's why.
[00:06:12]
That's why.
[00:06:14]
It feels like a pain and it is a hard part when you're starting out, but once you're
[00:06:20]
in the habit of doing it, I think it's fine.
[00:06:23]
It really brings a lot of guarantees about the code that you're working with.
[00:06:28]
You see the value after a while.
[00:06:29]
Let's put it that way.
[00:06:31]
Yeah.
[00:06:32]
It's the kind of thing.
[00:06:33]
I hear this over and over again from people that they thought it was a nuisance when they
[00:06:38]
first started and over time they grew to really love it and when they go to other languages,
[00:06:43]
they even try to replicate it.
[00:06:45]
There are some libraries out there that try to bring this pattern to TypeScript, for example.
[00:06:50]
Yeah.
[00:06:51]
How am I supposed to trust the HTTP request response?
[00:06:55]
Exactly.
[00:06:56]
I need to validate it and that's pretty much what we do with JSON decoders except that
[00:07:01]
it's enforced by the language or the language.
[00:07:04]
Right.
[00:07:05]
There's no way to tell Elm, just trust me, this is this type and just do your best and
[00:07:12]
blow up if I'm wrong about this.
[00:07:14]
Although there is actually one place where you can do that, but it's not a recommended
[00:07:18]
practice with a port.
[00:07:20]
You can describe, you got it.
[00:07:23]
Ports and flags, which you could say is like a special case of a port, but you can just
[00:07:29]
annotate your port or your flags as saying this takes a record with fields of these types
[00:07:36]
and Elm will just blow up if those assumptions are invalid.
[00:07:41]
Yeah.
[00:07:42]
And then you will not even get the message.
[00:07:44]
So that's one difference with implicit decoding and explicit decoding.
[00:07:50]
Explicit decoding, you will have to handle the error.
[00:07:53]
With implicit decoding, you will get a runtime error that will be shown in the console, but
[00:07:57]
you will not notice in the Elm code.
[00:08:00]
You will not be able to respond to the error.
[00:08:02]
Right.
[00:08:03]
And when you say runtime error, we should clarify your app dies completely, unrecoverably.
[00:08:10]
Yeah.
[00:08:11]
For flags, yes, but not for ports.
[00:08:13]
For flags.
[00:08:14]
Right.
[00:08:15]
I'm pretty sure it doesn't crash for ports.
[00:08:19]
Otherwise leave that in the comments.
[00:08:23]
Let's look that up and leave it in the comments.
[00:08:26]
What the result is there.
[00:08:27]
Yeah, that's a good question.
[00:08:28]
It's a good one to ponder.
[00:08:30]
You don't trust me.
[00:08:34]
You don't trust me.
[00:08:36]
I trust but verify.
[00:08:40]
The thing is that I'm actually doubting myself, so I'm not even trusting myself.
[00:08:46]
So I'm going to have to doubt and verify and then trust myself again.
[00:08:50]
Like, yes, you were right.
[00:08:52]
Or I'm just going to listen to this and say, yeah, no, he was wrong all along.
[00:08:57]
This is what I'm saying is like, we should all be a little more like Elm maybe, or maybe
[00:09:01]
it's good to be trusting people.
[00:09:03]
But just be like, hey, listen, I trust you, but let's just follow this process so we don't
[00:09:09]
have to even question trusting or not.
[00:09:13]
Make sure.
[00:09:14]
Yeah, I agree.
[00:09:15]
Okay.
[00:09:16]
So we kind of talked about this benefit of JSON decoders that it validates your data
[00:09:24]
and you know once you have this typed data, you can trust it and that's great.
[00:09:31]
But there's another benefit and I think this is maybe a more subtle one, but it's one that
[00:09:36]
as you get experience with Elm, I think people really grow to love this part of JSON decoders
[00:09:42]
as well, which is that it allows you to decouple the way that you represent your data from
[00:09:48]
the way that you serialize and deserialize your data.
[00:09:52]
And so, for example, what you find often in JavaScript code is because JSON is this first
[00:09:59]
class citizen, you have JavaScript object notation and you're in JavaScript.
[00:10:03]
So hey, I've got this JavaScript object, I can just pull data off of it, I can just reference
[00:10:09]
these fields and that's quite convenient on the one hand.
[00:10:13]
But on the other hand, maybe you have a format that isn't the best way to describe something
[00:10:17]
or maybe you want to have certain data structures that say make impossible states impossible
[00:10:23]
or better match the rest of your code base.
[00:10:27]
So let's talk a little bit about how that looks in JSON decoders.
[00:10:31]
What are the practices that allow you to decouple the serialization format from the representation
[00:10:38]
of that data in your Elm code?
[00:10:40]
Basically when you decode something, when you decode JSON, you'd set it, hey, I want
[00:10:46]
this field with this name and this type.
[00:10:49]
And then what you do is you apply a function to that, to the results of that, which is
[00:10:55]
from the raw value into something that you control.
[00:10:58]
So what you usually do is we create a new type, so a type alias, which represents what
[00:11:03]
you want, but that could have different names or you create opaque types over which you
[00:11:09]
have much more control and which can give you a lot of guarantees.
[00:11:14]
And that's it.
[00:11:15]
Right.
[00:11:16]
So let's maybe get into a concrete example.
[00:11:19]
So let's say that you've got some way of representing a date time on the server and you have some
[00:11:26]
way of serializing that into JSON so you can send it from the server to the client.
[00:11:31]
And then in your Elm code, you have some way to represent that date time.
[00:11:36]
Well, all three of those may be different.
[00:11:39]
And so if you're using an ISO 8601 string to represent it in the JSON payload that comes
[00:11:46]
through to the client, what I see happening in a lot of JavaScript applications is they'll
[00:11:50]
just store that as the value that they pass around all over the place.
[00:11:54]
So you're passing around these strings, but what you really want to do is you want to
[00:11:57]
wrap that into a nice data type that represents the date time as soon as you get the response.
[00:12:04]
And so like if you're writing a JSON decoder in Elm, let's say you've got some field created
[00:12:10]
at and it's an ISO 8601 date time.
[00:12:14]
Well, you can use your function to parse that from a string and then map that string into
[00:12:22]
an Elm time POSIX value.
[00:12:24]
Like Richard Feldman has a library for doing just that.
[00:12:27]
In fact, he even exposes a decoder that first decodes it as a string and then it maps that
[00:12:33]
string into an Elm time by running a parser that will extract out that ISO 8601 time format
[00:12:41]
into something that Elm can interpret as a POSIX time.
[00:12:44]
And so now the really cool thing is there's not a single point in your Elm code where
[00:12:50]
you have access to that string value.
[00:12:53]
Yeah, that's basically the point of don't model your model based on what you get from
[00:12:58]
the user or from the HTTP, from the server, but model it based on what you will do with
[00:13:06]
it.
[00:13:07]
So if the only thing that you're going to do with time is get the number of the year,
[00:13:11]
the year number, then just get that data.
[00:13:15]
If you need to display that number as a string, you can already store it as a string.
[00:13:21]
If you need to do operations on it, then store it as an integer and then whatever fits your
[00:13:26]
use case the best.
[00:13:28]
Do not store it as a string, as a POSIX, if it does not make sense to what you're going
[00:13:35]
to do with it afterwards.
[00:13:37]
That's interesting.
[00:13:38]
Yeah, I think there's a certain mindset that you get into.
[00:13:42]
So for me, test driven development is an example of this mindset.
[00:13:47]
There's a notion called programming by intention.
[00:13:50]
And all that term means is that you first sort of express how you want to use something
[00:13:56]
and then you reverse engineer that to figure out what the implementation looks like.
[00:14:01]
So you're thinking about how you want to consume something before you actually write the implementation,
[00:14:06]
which means the implementation details don't guide the high level data modeling or API.
[00:14:12]
Yeah, so this is kind of the same thing as API driven.
[00:14:17]
That's a good way to think of it.
[00:14:18]
Yeah, I mean, it's when you're doing data modeling, you know, I mean, you could even
[00:14:24]
start by just writing out your data type.
[00:14:27]
This is what I want to receive from the server.
[00:14:30]
What would my ideal data be?
[00:14:32]
This is actually something you can do with JSON decoders.
[00:14:35]
You can sort of incrementally build something out and start consuming hard coded data.
[00:14:40]
Yeah.
[00:14:41]
So a really good trick in the toolkit of JSON decoders is JSON.
[00:14:46]
It's a function called JSON decode succeed.
[00:14:49]
And what that function does, it's extremely useful for a number of reasons.
[00:14:54]
But one of the most useful use cases for it is to stub out data and just pass in hard
[00:15:01]
coded data.
[00:15:02]
So you could say, I need to get the year that this album was released.
[00:15:08]
There's like some music recording, and I need the year it was released.
[00:15:13]
And when I look at the Spotify API documentation, it says something about an ISO 8601 date time.
[00:15:19]
But all I know is I only care about the year something was released because I'm making
[00:15:23]
an app that takes this Spotify response.
[00:15:27]
And it lists out a listing of albums for all of these tracks and what year they were released
[00:15:33]
and orders them by year.
[00:15:35]
And it doesn't display the date or the month it was released.
[00:15:39]
It only shows the year.
[00:15:40]
And that's all I care about.
[00:15:41]
So ideally, my data structure would just be type alias album is a record.
[00:15:46]
And it's got a field called release year, which is an int.
[00:15:50]
And it's got a field called name, which is the album name, something like that.
[00:15:55]
Okay, let's start with that.
[00:15:56]
We've got those two fields.
[00:15:58]
Now we can write a JSON decoder that decodes data into an album.
[00:16:02]
So what does that look like?
[00:16:03]
Well, it's it's quite easy.
[00:16:04]
You can say JSON dot decode dot succeed 1975.
[00:16:09]
And now that's a JSON decoder that decodes into an int.
[00:16:12]
Well now I want to build that up into a JSON decoder that gives me an album, not just an
[00:16:17]
int.
[00:16:18]
So how do you do that?
[00:16:19]
Well, you say, now we need to decode it into an object into into a record.
[00:16:25]
So we can do that by taking our type alias we defined, we can say JSON dot decode dot
[00:16:29]
map two and give it our album constructor, our record constructor defined by our type
[00:16:35]
alias map to use two arguments map to because we're passing into arguments.
[00:16:41]
And now we give it our two decoders for a hard coded JSON decode succeed 1975 and a
[00:16:48]
JSON decode succeed some album name.
[00:16:51]
So that's a lot of code for a podcast, but the point is that and actually, I kind of
[00:17:00]
did something like this in my Elm Europe talk, incremental type driven development, illustrating
[00:17:06]
this technique of succeeding values and having hard coded things and consuming them as quickly
[00:17:11]
as possible.
[00:17:12]
So you can sort of drive the design of your API by prioritizing things that help you define
[00:17:17]
the API rather than the implementation.
[00:17:20]
So you defer the implementation because you want to discover what the ideal API looks
[00:17:25]
like as fast as possible.
[00:17:27]
Yeah, I was in the front row for that talk.
[00:17:31]
I remember you apologizing because you were taking too much time.
[00:17:37]
That was tough to squeeze into that time slot.
[00:17:41]
Live coding is hard, especially when you have to keep it short.
[00:17:44]
Yeah, try not to.
[00:17:46]
So yeah, that really helps out working with JSON data because you can just do it incrementally.
[00:17:54]
So as long as you can work with raw data and just display the rest of your album, for instance,
[00:18:01]
then you can just do that.
[00:18:02]
And then whenever you will try to make this dynamic, then you can start implementing the
[00:18:07]
decoder parts.
[00:18:10]
There was one thing that you mentioned a few minutes ago now, is about the ISO 8601.
[00:18:17]
Is that it?
[00:18:18]
Yes, that's the number.
[00:18:19]
You got it.
[00:18:20]
I got so impressed when you said it's like 86.
[00:18:24]
I typed it so many times.
[00:18:27]
It's ingrained in my brain.
[00:18:30]
So when you use that ISO 8601 something, then you decode it to a string and then you parse
[00:18:41]
it.
[00:18:43]
Parsing is kind of also the same idea as decoding, isn't it?
[00:18:48]
Yeah, exactly.
[00:18:50]
Decoding is kind of a special case of that.
[00:18:52]
Well it's basically decoding and parsing is the same thing, but you do not work with the
[00:18:59]
same raw data.
[00:19:00]
You have the same thing with other formats.
[00:19:03]
You could have YAML decoders.
[00:19:05]
I'm pretty sure there is a library with a YAML decoder actually from Thierry.
[00:19:10]
You could have a JSON decoder that you point at a GitHub repository and it will decode
[00:19:16]
an Elm repository and give you information about that project.
[00:19:21]
And you could say, decode primary programming language as a string.
[00:19:27]
And then you could do JSON.decode.end then take that string and you could do JSON.decode.fail
[00:19:34]
if the string is not Elm.
[00:19:36]
So you want to reject any non Elm project, right?
[00:19:40]
So that's the thing about JSON decoding is it's actually nothing magical.
[00:19:45]
It's just this one building block of JSON.decode.succeed and JSON.decode.fail.
[00:19:51]
It allows you to build a decoder that always succeeds or always fails.
[00:19:57]
If it always succeeds, it succeeds with the hard coded value you give it.
[00:20:00]
If it always fails, JSON.decode.fail, it always fails with the message you give it.
[00:20:07]
And so you can say if primary programming language does not equal Elm, JSON.decode.fail.
[00:20:15]
Or if it is Elm, JSON.decode.succeed, some value.
[00:20:19]
Yeah, the repository name or just something that says Elm rocks.
[00:20:25]
Yeah, exactly.
[00:20:27]
So that gives you the building blocks that you need to build something like an ISO 8601
[00:20:33]
decoder because you can just do whatever runtime checks you want.
[00:20:37]
That library happens to be built with an Elm parser, but that's just an implementation
[00:20:42]
detail if you built it with a regular expression or whatever, right?
[00:20:46]
It's just code.
[00:20:47]
Yeah, decoder is just a fancy name for saying a validator.
[00:20:55]
There are other things that we probably want to validate in our code, like forms.
[00:21:02]
I think it was someone called Lexi who made a post called, not Lexi, yes, who made a post
[00:21:11]
called parse don't validate or in the terms that we use now, decode don't validate.
[00:21:16]
So it's basically don't have if conditions that say if this field is this, then go ahead
[00:21:24]
and assume that everything is right, but instead decode form, like get a proper error.
[00:21:31]
Like in JavaScript, if you had if object has field something, if object has field name,
[00:21:39]
then take the name and pass it to this thing.
[00:21:42]
Well, no, you don't want to do that because the problem is if you just do an if condition
[00:21:48]
like that, then you haven't proven your work to the compiler.
[00:21:52]
And so the way I interpret this parse don't validate article is the idea is that you want
[00:21:58]
to be as you're proving certain conditions about your data, you want to show your work
[00:22:04]
so the compiler can keep track of that and keep refining down more constraints and more
[00:22:10]
guarantees about the data in a particular point.
[00:22:13]
Yeah.
[00:22:14]
And basically you validate each field and at the end you will have a whole form validator
[00:22:21]
in the form of a decoder.
[00:22:23]
Right.
[00:22:24]
A form validator in the form of a form decoder.
[00:22:27]
Yeah.
[00:22:28]
Yeah, it's like a decode style validator.
[00:22:31]
And there are a lot of different places that this sort of decoding pattern shows up.
[00:22:38]
Like I mean, for example, I built a package called Elm CLI options parser.
[00:22:44]
And what that library does is you can you can wire it into an Elm worker like a headless
[00:22:50]
Elm application wired in with node JS and then build a command line tool that parses
[00:22:59]
command line flags.
[00:23:00]
So if you say, you know, dash dash help or dash dash output to tell it where to output
[00:23:07]
your code or whatever.
[00:23:08]
So like I use that in the Elm GraphQL command line tool and other things.
[00:23:12]
Yeah, I wish I could have used it in Elm review, but I can't.
[00:23:17]
Oh, that's sad.
[00:23:18]
Because I'm building an Elm application.
[00:23:21]
I don't have an Elm application.
[00:23:23]
I see.
[00:23:24]
That might be a topic for another conversation because we'll see.
[00:23:28]
Maybe I can convince you to use it.
[00:23:30]
But the really nice thing that you get when you use that is that you have a guarantee.
[00:23:36]
You do this sort of decoder style pipeline where you say, okay, I have a command line
[00:23:40]
application and I have a flag called dash dash output.
[00:23:46]
And I expect it to be in this format and you can, you know, do whatever validations you
[00:23:51]
need to.
[00:23:52]
You can say, I expect it to be, you know, an int and I expect this thing to be an int.
[00:23:57]
I expect this string to be either this value or this value.
[00:24:02]
As you go and you build that up, you're basically describing the possible ways to invoke your
[00:24:08]
command line application, right?
[00:24:10]
And because you've described that, you've not only described it to my Elm CLI options
[00:24:16]
parser tool, but you've also described it to the Elm compiler, which means the Elm compiler
[00:24:23]
knows, Oh, based on all these chains that you've done, you added this field of this
[00:24:29]
type, you added this field of this type, you mapped it into this data structure, this record.
[00:24:35]
The Elm compiler knows what data type you're going to end up with.
[00:24:40]
If it succeeded, it knows that it's going to be a result of a given type, a string result
[00:24:45]
or whatever if it fails.
[00:24:47]
And my Elm CLI options parser tool can just guarantee you that you're either a going to
[00:24:53]
get the data you expected and you can just run the program for them or B the user gave
[00:25:00]
an invalid set of options based on your definition and the user is going to see an error message
[00:25:05]
telling them what went wrong, which is pretty cool.
[00:25:08]
Yeah.
[00:25:09]
What about when you don't want the code to fail?
[00:25:13]
Like it is fine if I don't have this flag or if this value is not well formed, but what
[00:25:20]
do you do then?
[00:25:21]
Well, you can define certain fields as optional and the baseline in Elm CLI options parser
[00:25:28]
is just a string.
[00:25:30]
I mean, ultimately everything in a terminal is just a string.
[00:25:35]
And so it's up to you to make additional guarantees or to use specific helpers that say, I expect
[00:25:41]
this to be this type or I expect this to be one of these discrete values.
[00:25:45]
And it can actually present documentation and it can give you better documentation if
[00:25:50]
you use these specific helpers.
[00:25:52]
But you can just say, give me the raw string input and I don't care what it is, just give
[00:25:58]
me the raw thing.
[00:25:59]
Yeah.
[00:26:00]
It's the same thing with JSON decoders.
[00:26:01]
So it's not necessarily black and white.
[00:26:03]
You don't have to have the whole thing crash or fail.
[00:26:08]
If one thing is missing, you can say, Hey, this is optional or this is okay if it doesn't
[00:26:14]
work, but then just use this default value for instance.
[00:26:17]
Right.
[00:26:18]
Yeah.
[00:26:19]
So there's this thing that often I still have to look up the documentation to remember this.
[00:26:25]
You can either say maybe decode.string.
[00:26:29]
Decode.maybe space decode.string, I think.
[00:26:33]
Yeah.
[00:26:34]
You can either say decode.maybe decode.string.
[00:26:37]
That gives you something that the string may or may not be null, right?
[00:26:42]
Or you can say, so you could decode a field that is a maybe string, but that field then
[00:26:48]
needs to be there.
[00:26:50]
So it would need to be, in the JSON, it would need to be name, colon, null.
[00:26:54]
Or you could say this field is optional.
[00:26:57]
So if this field isn't there, that's fine.
[00:27:00]
Just give me nothing.
[00:27:01]
Then you'd have to say maybe and then the field decoder with the string.
[00:27:07]
Yeah.
[00:27:08]
With the decode.string.
[00:27:09]
So that one kind of trips me up, but it's the kind of thing.
[00:27:11]
So it can feel overwhelming at first.
[00:27:14]
There's a lot to take in and it really hurts your brain.
[00:27:16]
But the thing is you want to start at the smaller level.
[00:27:21]
If you try to think about it as a whole, then it's too overwhelming.
[00:27:24]
But if you start by saying, hey, let's say I have a JSON response that comes back.
[00:27:29]
That's just a string, right?
[00:27:31]
That's JSON.
[00:27:32]
A string is JSON or an int is JSON.
[00:27:33]
Yeah.
[00:27:34]
How do I decode that?
[00:27:36]
Well you say JSON.decode.string and that will take whatever string, hello world in your
[00:27:42]
JSON response and it will give you hello world.
[00:27:45]
Or if you pass it a float, then it will say error.
[00:27:49]
I expected a string.
[00:27:51]
So start with that.
[00:27:52]
You know, you start with that and then you say, okay, well can I decode a string and
[00:27:58]
an int in a JSON object with these field names?
[00:28:01]
You know, you start with that and you build it up one step at a time.
[00:28:05]
And actually Brian Hicks has a book called the JSON Survival Kit.
[00:28:10]
It's a pretty quick read.
[00:28:12]
I think it's a really good resource if you're getting started with JSON decoders to learn
[00:28:15]
about this.
[00:28:16]
And he talks about this process of building it up piece by piece to get more complex ones,
[00:28:22]
which I think is the way to learn it.
[00:28:24]
Okay.
[00:28:25]
Yeah.
[00:28:26]
I haven't gone through that one.
[00:28:27]
I probably should.
[00:28:28]
Joel Keneville.
[00:28:29]
Yes.
[00:28:30]
Also has a few blog posts on the subject that we will link to because I know that he sends
[00:28:34]
them, he links to them a lot on Slack and they seem to help people a lot.
[00:28:40]
Definitely.
[00:28:41]
Yeah.
[00:28:42]
It's hard to get started, but just, I think that trying to write JSON decoders top down
[00:28:49]
is kind of overwhelming.
[00:28:50]
So start bottom up, start with the smallest piece of your JSON decoder and get that working.
[00:28:58]
And once you have that working, you can even write a unit test for that if you want, or
[00:29:03]
you can just try working with some smaller piece of data and then build up from there.
[00:29:08]
There's one thing that I'm not very fond of with JSON decoders is that my Elm application
[00:29:15]
won't crash.
[00:29:16]
It will work exactly as I planned it to.
[00:29:18]
And that's great because every HTTPS response that I get will be validated.
[00:29:24]
The thing is, if it's not valid, I will not necessarily know it from a developer's point
[00:29:30]
of view.
[00:29:31]
I will have to handle it at runtime and I have no guarantees that the decoding will
[00:29:36]
work or yeah, that's my biggest issue that I don't know if the decoding will work when
[00:29:42]
I compile.
[00:29:43]
Yes.
[00:29:44]
Yeah.
[00:29:45]
What do you do for that?
[00:29:47]
Well, now that you put it that way, I'm kind of realizing that the main selling point of
[00:29:54]
two projects I've built is exactly that.
[00:29:59]
The first one is Elm GraphQL and Elm GraphQL gives you a way to, if you're not familiar
[00:30:06]
with GraphQL, it's basically a schema for your API.
[00:30:11]
So it's a schema that describes all of the possible API calls you can get.
[00:30:15]
And the graph part is that you can sort of traverse these relationships between different
[00:30:20]
parts of the API.
[00:30:21]
You can go to a user and you can see their friends or their likes and traverse to these
[00:30:27]
different objects.
[00:30:28]
But the part that we're most interested in as Elm developers, that part's great too,
[00:30:33]
but it gives you a schema.
[00:30:35]
So you have types describing all the different values you can get from the API.
[00:30:39]
Yes.
[00:30:40]
And so you can build up a response and it's known before you run your query what data
[00:30:48]
you're going to get back.
[00:30:49]
So Elm GraphQL generates code that allows you to consume your specific GraphQL API in
[00:30:56]
a way where you know that the types are going to be correct based on the schema of your
[00:31:00]
API that GraphQL gives you.
[00:31:02]
So it's basically taking that knowledge that your GraphQL schema has and bringing it down
[00:31:07]
so the Elm compiler also has that knowledge.
[00:31:10]
If you want to get a better picture of what that looks like exactly, I gave a talk at
[00:31:14]
Elm Conf a couple of years back called Types Without Borders.
[00:31:17]
That's worth checking out if you want to get an overview of that concept.
[00:31:21]
So that's one way that you can do that.
[00:31:24]
The other one, which we did our first episode on Elm Pages and we talked about static HTTP,
[00:31:30]
actually that's one of the things that I find most exciting about the Elm Pages project
[00:31:34]
is that it gives you this tool static HTTP, which allows you to run your decoder at build
[00:31:40]
time and get this data from HTTP APIs.
[00:31:46]
And if any of your decoders fail, your users don't see it.
[00:31:49]
You see it in your build pipeline.
[00:31:51]
Your build tool will fail, your CI build will fail, but your users will not see a failure
[00:31:58]
and that's guaranteed, which is pretty exciting to reduce the possible failures that could
[00:32:05]
happen at runtime.
[00:32:07]
But I'm not sure, Jeroen, did you have any other thoughts on that topic of how you can...
[00:32:11]
Yeah.
[00:32:12]
So what happens when you don't have a schema?
[00:32:15]
That's where things are getting tricky.
[00:32:18]
Because you don't have a schema to generate something from or to compare against.
[00:32:23]
So you basically have to kind of unit test and hope the server will match what you expect,
[00:32:31]
I guess.
[00:32:32]
Right.
[00:32:33]
And that is where the limits of Elm end, I guess.
[00:32:36]
Yes.
[00:32:37]
Right.
[00:32:38]
There's just conceptually, there's nothing Elm can do there.
[00:32:42]
Tools like Elm GraphQL can help.
[00:32:44]
There are similar things for Haskell, like Haskell Servant, is that what it's called?
[00:32:49]
That allows you to take the data types you're returning on some Haskell server and it generates
[00:32:55]
types and decoders for your Elm code.
[00:32:58]
Yeah, I don't remember the name, but there were some decoding generators and type generators
[00:33:03]
actually.
[00:33:04]
Yes.
[00:33:05]
I think it makes a lot of sense to...
[00:33:07]
I mean, that's why I named the talk I gave Types Without Borders, because I think it
[00:33:11]
does make sense to sort of connect across these different boundaries of these different
[00:33:16]
languages and runtimes and say like, okay, we're in different runtimes, but that doesn't
[00:33:21]
mean we can't share information about what type of data you should expect.
[00:33:25]
Yeah.
[00:33:26]
I might be going on a tangent here, but you're tying yourself up to the response of the backend.
[00:33:31]
So if the backend changes, the decoders will change, but all the versions of your client
[00:33:36]
side will not work anymore.
[00:33:39]
Right.
[00:33:40]
So I could easily do a whole episode about this specific topic even, not even just Elm
[00:33:45]
GraphQL, but the short...
[00:33:47]
I'll give you the short version, which is that there are two pieces here.
[00:33:52]
One piece is that there are some practices around building GraphQL APIs where people
[00:33:57]
try to build them in a nonbreaking way.
[00:33:59]
This is just a sort of cultural value in the GraphQL community that you try to have nonbreaking
[00:34:06]
API changes.
[00:34:08]
So that means that you could, instead of removing a field, you would add a new one and maybe...
[00:34:16]
Well, see, I have some disagreements with certain parts of this because part of what
[00:34:21]
that implies is that you make every field nullable, which isn't great.
[00:34:25]
And so they say, okay, well it's nullable.
[00:34:27]
So if you stop returning certain fields or if something fails, then your whole assumptions
[00:34:33]
don't fail because you're treating everything as nullable.
[00:34:36]
That doesn't feel like a great solution.
[00:34:37]
But that said, there are certain ways that, okay, if you have a new required argument,
[00:34:42]
that's a breaking API change.
[00:34:45]
And so if you're going to do that, then maybe you make a new field and that field has that
[00:34:50]
required argument.
[00:34:51]
And then you deprecate the old version.
[00:34:53]
And there is a way in your GraphQL schema to deprecate certain fields.
[00:34:56]
So that's one thing.
[00:34:57]
And you can sort of have a path.
[00:34:59]
Maybe if you decide to make a breaking API change, maybe you at least give a nice transition
[00:35:04]
path where you have a deprecation period, and then if you go to a new version or do
[00:35:09]
a breaking change, you give some time for clients to get updated or whatever.
[00:35:13]
Yeah.
[00:35:14]
For REST endpoints, you could duplicate the endpoints.
[00:35:16]
So you do that way.
[00:35:20]
You don't have a breaking change, just new requirements.
[00:35:22]
Right.
[00:35:23]
And the second piece, and I know some people who are doing this is you can take a snapshot
[00:35:30]
of your GraphQL schema.
[00:35:33]
And if you ever have a breaking change, first of all, there's tooling that can tell you
[00:35:36]
if you're making a breaking change to your GraphQL schema, which is cool.
[00:35:40]
Oh, cool.
[00:35:41]
Yeah.
[00:35:42]
It could warn you.
[00:35:43]
And then what you could do is you could kind of take a snapshot every time that happens.
[00:35:47]
If the browser client is on a version between a breaking change, you could say increment
[00:35:53]
a number every time there's a breaking change and you could have the client know which number
[00:35:59]
it's on and then check before it makes a request if it's on an outdated version.
[00:36:04]
And then it has to reload itself before it continues if there's been a breaking change.
[00:36:09]
Yeah.
[00:36:10]
Mario Rogic had a talk kind of like that.
[00:36:14]
Yes.
[00:36:15]
On Evergreen.
[00:36:16]
Yeah.
[00:36:17]
I really like his thoughts on that.
[00:36:18]
That's a really cool concept.
[00:36:19]
Yeah.
[00:36:20]
We'll link to that.
[00:36:21]
Yes, definitely.
[00:36:23]
Another thing we haven't touched on, but we've talked about generating types confidently
[00:36:29]
with tools like Elm GraphQL or Haskell Servant.
[00:36:34]
What about auto generating JSON decoders?
[00:36:38]
There are a lot of tools out there for copy pasting a JSON payload into a window and generating
[00:36:44]
some Elm decoders or there are some editors.
[00:36:48]
IntelliJ Elm has some tooling that lets you generate a decoder based on a type annotation,
[00:36:53]
for example.
[00:36:54]
What do you think about those?
[00:36:56]
Are they worth using?
[00:36:57]
Do you use them?
[00:36:59]
I don't use them because I usually don't have access to those.
[00:37:02]
I don't have the need for it.
[00:37:04]
I think they can be a very good starting point.
[00:37:08]
The thing is that you...
[00:37:11]
What do you decode into?
[00:37:12]
You decode in something that looks like what you have in the backend.
[00:37:16]
So I think they're useful, but only if you transform it afterwards into something that
[00:37:21]
is made for the client.
[00:37:23]
So what we discussed sometime in the episode.
[00:37:26]
You want something that you will use, not something that looks like what the backend
[00:37:30]
returns.
[00:37:31]
Right.
[00:37:32]
So it's like coupling you to the serialization format and it's getting you thinking about
[00:37:38]
the serialization format first rather than your ideal data structure.
[00:37:42]
Exactly.
[00:37:43]
And you don't want that.
[00:37:44]
You can map over what you got at decode time.
[00:37:49]
So add another layer of decoding and that will be fine in my book.
[00:37:53]
But you don't want to tell that coupling impact the rest of your application.
[00:37:57]
So I think it's a very good starting point if you have trouble making them yourself,
[00:38:02]
but I don't use them myself.
[00:38:04]
That's interesting that you say that it would be fine in your book if I could get into some
[00:38:09]
nitpicking and maybe explore something where we have a different perspective on things.
[00:38:13]
Yeah, I don't have a book.
[00:38:14]
Okay.
[00:38:15]
That's nitpick.
[00:38:16]
That's it.
[00:38:17]
Yeah.
[00:38:18]
Yeah.
[00:38:19]
In your book.
[00:38:20]
It's okay.
[00:38:21]
Yeah, yeah, in your, it's okay in your blog.
[00:38:24]
That's the definitive source of your own opinions.
[00:38:28]
So I would consider that a smell in my book or in my blog.
[00:38:32]
And the reason is because, I mean, partially because of what we talked about to sort of
[00:38:37]
parse don't validate, I want to do it in a single step.
[00:38:42]
I want to just have this format and deserialize it into exactly what I want.
[00:38:48]
And we talked about this in the opaque types episode, this notion, I use this term, wrap
[00:38:54]
early unwrap late.
[00:38:56]
I want to wrap as early as possible.
[00:38:58]
In fact, if I can wrap in the appropriate custom types and nice data structures and
[00:39:04]
everything before I even have access to the data, that's the ideal, right?
[00:39:09]
So yeah, I totally agree.
[00:39:11]
Are you just saying like, it's not a big deal, but it's a best practice to decode into your
[00:39:16]
desired data type or what were your thoughts on that?
[00:39:20]
If I was understanding correctly, you were saying that it's okay in your book to auto
[00:39:25]
generate a decoder and then get some sort of data format that represents the serialization
[00:39:31]
format of the JSON and then pass that to a function and then map that into a different
[00:39:37]
data structure as a separate step.
[00:39:40]
You couldn't do it that way.
[00:39:41]
The thought I had was just generate the decoders for each field and then the function that
[00:39:47]
will create your custom type or your record, that one should make it look like what you're
[00:39:54]
going to use.
[00:39:55]
But you could have one extra step and just get that, have that logic of mapping one to
[00:40:01]
the other in a decoder.
[00:40:03]
But yeah, you don't want it to leak out somewhere else.
[00:40:06]
So it's basically what is good in your book.
[00:40:10]
Okay, so we're on the same page in our books there, it sounds like.
[00:40:14]
Well, you also don't have a book.
[00:40:16]
Well, we'll see about that.
[00:40:18]
We'll see.
[00:40:19]
Oh, yeah.
[00:40:20]
Sneak peek.
[00:40:21]
Yeah.
[00:40:22]
Keep your eyes out.
[00:40:23]
There may be a book.
[00:40:24]
There may be a book.
[00:40:25]
So okay.
[00:40:26]
Yeah.
[00:40:27]
And one of the other really cool things that comes from this strategy of immediately getting
[00:40:33]
the data type you want rather than having this intermediary data format in your Elm
[00:40:38]
code.
[00:40:39]
It's really nice because you can sort of locally reason about how you want to transform something.
[00:40:45]
Like I remember back in the day working on some angular code.
[00:40:49]
Oh, man, it was it was fun.
[00:40:51]
It was lots of fun just going through this data that we were getting from the server
[00:40:57]
and running all these functions to filter over and change certain bits of data into
[00:41:04]
a format that we needed to match it against some filters that we were applying and things
[00:41:09]
like that.
[00:41:10]
So we needed it into a specific format.
[00:41:12]
And so there's this one giant function that takes all this data, this deeply nested data
[00:41:18]
structure, and then it reaches into these pieces and it mutates things and it maybe
[00:41:24]
it does some sort of functional style mapping of things.
[00:41:28]
But either way, it's not it's it's very error prone and it's a pain and it's really it really
[00:41:34]
hurts your brain a lot more than it needs to.
[00:41:38]
And so this is one of the best qualities of JSON decoders, I think, is when you can just
[00:41:42]
say, hmm, well, I've got this big data structure that's coming back from the server.
[00:41:47]
It's got all of the users who are online right now and it's got some information about those
[00:41:52]
users like their current status, and then it's got some nested data structures like
[00:42:00]
which rooms they're a member of or whatever.
[00:42:03]
Right.
[00:42:04]
And you say, well, we're going to change something about the way that we're dealing with the
[00:42:08]
rooms that they're in or we want to, you know, add an additional piece of data to that or
[00:42:13]
put it in a different data structure or whatever.
[00:42:15]
Well, where do you go?
[00:42:16]
You could have like one module that deals with that piece of it.
[00:42:20]
And it would be opaque.
[00:42:21]
It could even be opaque.
[00:42:23]
Yes.
[00:42:24]
Yes.
[00:42:25]
Guarantees.
[00:42:26]
Exactly.
[00:42:27]
It gives you these guarantees.
[00:42:28]
You can you can just have it as a separate concern and you can reason about it locally.
[00:42:32]
You can have unit tests that say, hey, here's this piece of this giant data structure.
[00:42:37]
For performance reasons, we get it back as one giant JSON blob from the server.
[00:42:42]
But in terms of reasoning about it, I want to think about this piece of it as a unit.
[00:42:47]
And perhaps I even want to reuse that piece when we get a different response from the
[00:42:51]
API, when we ask for a specific part of it.
[00:42:54]
And so you can sort of like you can separate these pieces out.
[00:42:58]
You can use nice opaque types.
[00:43:00]
You can get nice test cases on them.
[00:43:02]
You can change it locally without going to this one big function that maps everything.
[00:43:07]
Like the thing that you have these things co located, the things that like decode into
[00:43:12]
this data, the things that like validate this part of the data and know about the JSON structure
[00:43:19]
of this data and put it into the right format.
[00:43:21]
All of those responsibilities are sort of cohesively together and you can isolate them
[00:43:27]
from the from the rest of it.
[00:43:29]
And then you can snap together these different decoders that you've isolated if you need
[00:43:33]
to reuse them.
[00:43:34]
So I find that it's just a really nice way to organize and structure your data and to
[00:43:40]
think about your code.
[00:43:41]
Yeah, I think you say that because you're used to modeling your modules by data type.
[00:43:48]
So there is something that Evan advises for in this talk, The Life of a File, which is
[00:43:56]
very good advice in my opinion, in my book, my blog.
[00:44:02]
We have one concern around the data type.
[00:44:05]
What you do is you put that data type into a separate module and everything will be cohesively
[00:44:10]
grouped together, as you said.
[00:44:12]
And that is just something that you need to start getting into your head into your habits
[00:44:17]
of grouping things and putting them into a separate module instead of having one giant
[00:44:24]
file that does everything.
[00:44:26]
Otherwise you'll get the same problem as you with your Angular application.
[00:44:30]
Right.
[00:44:31]
Right.
[00:44:32]
And because you can still pretty hard to reason about if you want to.
[00:44:38]
You can definitely create premature abstractions where you're going crazy with because you
[00:44:43]
don't necessarily know what's going to be grouped together naturally.
[00:44:46]
But the point is that you can, the way that JSON decoders work, you can reason about it
[00:44:51]
locally because it is this sort of way of transforming data.
[00:44:56]
And again, this is just like a broader pattern that you can have everything about how you
[00:45:02]
deserialize this data.
[00:45:04]
What are the names of the fields?
[00:45:05]
What are the raw data types you expect?
[00:45:07]
How do you transform those raw data types?
[00:45:09]
Those things just belong together and you can sort of hide them in this box and change
[00:45:16]
them in this one place and think about that as one unit.
[00:45:20]
Even though it fits into this bigger piece of decoding a giant JSON blob, you can think
[00:45:25]
about this one small part of it.
[00:45:26]
So it's just a very successful pattern.
[00:45:29]
And again, it's a pattern that shows up all over the place in Elm code.
[00:45:34]
So, you know, start paying attention to that broader pattern when you have a function for
[00:45:40]
succeed or fail that can stop the validation and say something went wrong.
[00:45:46]
Here's the error message to show or, Hey, just don't even try running any real validations
[00:45:53]
or anything like that.
[00:45:54]
Just give this value or you can have actual decoders or validators or whatever, be on
[00:46:00]
the lookout for that pattern.
[00:46:02]
There was another point that I think is interesting on this topic.
[00:46:06]
We talked about, should you generate your decoders?
[00:46:09]
And we talked about these different tools for that.
[00:46:11]
As I've gotten used to writing decoders in Elm, at first I found them intimidating.
[00:46:17]
And once I got some practice generating, it doesn't seem that useful to me because it's
[00:46:23]
so easy to write it.
[00:46:24]
I can just like write the code and it's not that big a deal because you have enough practice
[00:46:28]
and you become comfortable with those concepts.
[00:46:31]
But it takes time and it's good to like break things down.
[00:46:36]
So some people who are new to Elm say, well, why can't we use something like this approach
[00:46:41]
that Haskell uses where you can automatically generate something that decodes data based
[00:46:46]
on the data type.
[00:46:47]
So Evan has a really interesting document on this.
[00:46:51]
It's called a vision for data interchange in Elm.
[00:46:54]
And he touches on that and he talks about his experience using these automatically generated
[00:47:00]
decoders and he says, well, yeah, you can do that.
[00:47:03]
But in my experience, debugging an error when it's coming from an implicitly generated thing
[00:47:09]
based on the type signature is a lot harder to debug because I don't know where to look.
[00:47:16]
I don't know how to change it to make it work.
[00:47:20]
So that's another piece of it.
[00:47:21]
The fact that JSON decoders are explicit is very in line with the values of Elm, which
[00:47:26]
is there are worse things than being explicit.
[00:47:28]
Like is that really the bottleneck to you writing maintainable, easy to change and not
[00:47:34]
so error prone code?
[00:47:37]
Like that's not the problem.
[00:47:39]
Spoiler plate.
[00:47:41]
That's not the thing keeping you from moving fast in your application.
[00:47:43]
The thing keeping you from moving fast is something changes on your server the way that
[00:47:48]
your code is serialized and now your decoders broke, but they were being generated implicitly
[00:47:54]
based on your type signatures.
[00:47:56]
And how the heck do I fix it?
[00:47:58]
I explained that to your backend engineers.
[00:48:01]
You broke it.
[00:48:02]
They're like, what?
[00:48:05]
That's your problem, frontend developer.
[00:48:07]
Yeah.
[00:48:08]
Yeah.
[00:48:09]
Yeah.
[00:48:10]
The JSON decoders get the decoupling really nicely.
[00:48:14]
It's just a really elegant pattern.
[00:48:16]
And once you get used to it, you'll learn to love them.
[00:48:18]
Maybe it's Stockholm syndrome, but we're too far gone at this point.
[00:48:22]
I feel like I never had much trouble with JSON myself.
[00:48:27]
That's because my first work on Elm was working on Elm Lint, the previous name for Elm review.
[00:48:34]
So it was something not frontend related for months, maybe years before I really started
[00:48:39]
working on frontend work.
[00:48:43]
And I just kept seeing people say, wow, JSON decoders are really hard.
[00:48:48]
How do you do this?
[00:48:49]
How do you handle this case?
[00:48:52]
And I just read all the Slack conversations and I learned that way.
[00:48:58]
Interesting.
[00:48:59]
That was very nice for me.
[00:49:01]
Yeah.
[00:49:02]
That makes sense.
[00:49:03]
I mean, I think one of the things that can be hard to wrap your brain around with JSON
[00:49:07]
decoders when you're new to Elm is sometimes it feels like, why can't I just have a JSON
[00:49:12]
decoder?
[00:49:13]
Why can't I build one up by passing a list of JSON decoders somewhere?
[00:49:18]
And then it gives me like, oh, here was a JSON decoder for an int and a JSON decoder
[00:49:23]
for a string.
[00:49:24]
And just give me a JSON decoder from that list that I passed to you.
[00:49:28]
But the way that Elm works, you have to understand how these types change as you apply functions.
[00:49:34]
And if you just pass in a list of things, then those things must have the same type
[00:49:41]
for one thing.
[00:49:42]
So you can't just have these different things and have it transform the type signature.
[00:49:48]
So it's a pretty advanced thing actually that takes some time when you're new to Elm to
[00:49:53]
get this sense of how calling functions also massages the types little by little.
[00:49:59]
So you're saying, here's a JSON decode dot string.
[00:50:03]
But well, I actually want to take this string and I want to extract the year from this ISO
[00:50:11]
8601 date.
[00:50:12]
I still get so impressed when you say that.
[00:50:19]
It feels like magic.
[00:50:20]
Say it again.
[00:50:22]
It's just a manifestation of the trauma I've been through dealing with that type too much.
[00:50:29]
But...
[00:50:30]
Extract the year, you said?
[00:50:33]
Yeah.
[00:50:34]
So if you're trying to extract the year, you have to understand that you take this JSON
[00:50:40]
decode string and then you apply a JSON decode map function, which is going to take a string
[00:50:46]
decoder.
[00:50:48]
So it's going to take a value of type decoder string and it's going to transform that into
[00:50:53]
a type decoder int.
[00:50:55]
And so you can apply that and transform the type of your decoders.
[00:50:59]
And then you can take, you know, you could take something if you do map two, you could
[00:51:03]
take something that takes a decoder of type string and a decoder of type int and it decodes
[00:51:09]
it into a decoder of type album, which is our type alias for the album record type.
[00:51:16]
So I mean, I don't know, it's the kind of thing that when you're just saying it, it
[00:51:19]
sounds so simple, but it takes time for your brain to get used to that, to how these types
[00:51:24]
sort of fold together and how applying these map functions transforms things.
[00:51:31]
Yeah.
[00:51:32]
Well, as you said, it's an advanced topic.
[00:51:35]
The problem with them is that they are an advanced topic, although they're not that
[00:51:41]
hard when you get used to it.
[00:51:43]
So they're advanced, but they happen at a point in your learning that is pretty early
[00:51:47]
on.
[00:51:48]
Exactly.
[00:51:49]
So people who are used to doing JavaScript front end development, for instance, they're
[00:51:53]
used to making HTTP calls.
[00:51:55]
So they're used to getting data from the server.
[00:51:58]
And that's one of the first things they will try to do before other things.
[00:52:02]
Yeah.
[00:52:03]
Yes.
[00:52:04]
I completely agree that you couldn't have said it better.
[00:52:07]
It's an advanced topic, but one that comes up right away.
[00:52:10]
Maybe in some cases, for some people, that will be one of the first things they will
[00:52:14]
try actually.
[00:52:15]
Exactly.
[00:52:16]
Don't do that.
[00:52:17]
Right.
[00:52:18]
Right.
[00:52:19]
Okay.
[00:52:20]
So a couple of tips for sort of easing that learning curve for JSON decoders.
[00:52:24]
At least I found this very helpful.
[00:52:26]
One thing is understand the JSON.decode.map function.
[00:52:31]
Play around with that.
[00:52:32]
And in fact, forget the map seven, map two functions.
[00:52:37]
Just try JSON.decode.map.
[00:52:40]
Take a string decoder and map that string into an int.
[00:52:44]
Like decode a JSON string into an integer value or fail if it's not a string that's
[00:52:50]
wrapping an integer.
[00:52:51]
Try something like that.
[00:52:52]
Just to give yourself a sense of how mapping feels, how you can write it, how you can transform
[00:52:58]
types in your pipeline.
[00:53:00]
That's one thing.
[00:53:01]
I think a second thing that people get tripped up on that's like a low level building block
[00:53:05]
of decoders that's really core to how you build decoders is record type aliases.
[00:53:11]
So one thing they don't teach you, it's sort of an implicit rule that isn't like you just
[00:53:18]
learn it by seeing it happen a lot.
[00:53:21]
When I say type alias album equals some record, it's actually giving me a type constructor
[00:53:28]
that takes if I have a field that's a string and a field that's an int.
[00:53:33]
In that order.
[00:53:34]
It gives me a function in that order.
[00:53:36]
It gives me a function which takes a string, an int and returns an album record.
[00:53:42]
Yeah.
[00:53:43]
And that's one of the few implicit things that Elm does for you.
[00:53:46]
Yes.
[00:53:47]
So I usually say Elm has no magic.
[00:53:51]
And that's pretty much true.
[00:53:52]
But that one is implicit.
[00:53:54]
Yes, it's quite handy.
[00:53:55]
But where do you learn that?
[00:53:58]
And so we're stating it here, Jeroen.
[00:54:01]
We're giving people this explicit knowledge when you say type alias album equals a record
[00:54:07]
alias, a record type.
[00:54:09]
It gives you a constructor function.
[00:54:11]
Okay, so yeah, pull up an Elm REPL, try defining a type alias of a record, and then just write
[00:54:18]
the name of that record that you defined, capital A album, and the type is function.
[00:54:25]
Yeah, just be very wary about the order of fields.
[00:54:29]
I try not to put two elements with the same type next to each other in the type alias
[00:54:35]
definition.
[00:54:36]
Exactly.
[00:54:37]
Because the problem with this is that you can put the right value in the wrong field.
[00:54:43]
You can decode things in the wrong position.
[00:54:46]
So be very wary of that.
[00:54:49]
Yes, right.
[00:54:50]
So if I had a JSON decoder that's decoding like a name JSON object, and that name has
[00:54:56]
first name and last name, and I have a type alias name is a record with first name and
[00:55:03]
last name.
[00:55:04]
If I have my JSON decoder working perfectly, and it's getting the right first name and
[00:55:08]
last name, and I now change the order, and I switch the order in my type alias.
[00:55:14]
Or in the JSON decoder, either.
[00:55:17]
Right.
[00:55:18]
Yes, that's right.
[00:55:19]
If I change the order in either place, so it might seem like, oh, I'm just refactoring
[00:55:23]
my code.
[00:55:24]
I maybe maybe you put.
[00:55:25]
No, you're not.
[00:55:27]
It's not that simple.
[00:55:29]
It's not that simple.
[00:55:30]
The order matters because you're using a positional argument constructor function that the type
[00:55:38]
alias gives you when you define a type alias.
[00:55:40]
It gives you a constructor function and it is order dependent.
[00:55:44]
Yeah.
[00:55:45]
So beware of that.
[00:55:47]
Beware.
[00:55:48]
That's the PSA for the day.
[00:55:50]
The more you know.
[00:55:51]
Anything else that people should know as they're getting started with JSON decoding?
[00:55:56]
Not on my blog.
[00:55:58]
All right.
[00:56:00]
Well, I think that's a good start and looking forward to getting into some other topics.
[00:56:06]
There are some more nuanced topics to explore here, but hopefully this is a good start for
[00:56:10]
everybody.
[00:56:11]
So we have recorded these episodes, the four episodes before we released Elm Radio to the
[00:56:16]
public before you even knew this was a thing.
[00:56:20]
And we would probably like to have some suggestions about topics.
[00:56:25]
We have plenty of things to talk about.
[00:56:27]
Many, many, many things that I will not list.
[00:56:31]
But it might be useful for us at some point if you gave us some topics you would like
[00:56:36]
us to cover.
[00:56:37]
Yeah, topics.
[00:56:38]
And we might do some grab bag episodes where we go through different questions, maybe multiple
[00:56:44]
questions in one episode.
[00:56:45]
So submit a question that you have and we'd love to talk about it.
[00:56:50]
Don't make them questions that you can just get an answer on on Slack.
[00:56:54]
Yeah, perhaps it's best to give questions where we can sort of get into some interesting
[00:56:59]
best practices or different ways of looking at a topic rather than just a here's one clear
[00:57:05]
cut answer that somebody on Slack could probably do a better job just linking you to the right
[00:57:09]
article quickly.
[00:57:10]
Yeah, exactly.
[00:57:11]
Or why is this code not compiling?
[00:57:14]
Like that's not what we're going to answer.
[00:57:17]
Right.
[00:57:18]
Yeah.
[00:57:19]
Cool.
[00:57:20]
Well, looking forward to seeing what people submit.
[00:57:22]
And yeah, thanks a lot.
[00:57:23]
You're in.
[00:57:24]
I'll talk to you next time.
[00:57:25]
See you next time.
[00:57:26]
We'll talk again soon.