Turning JSON strings into types, also known as JSON Decoding is an important part of using Elm. It allows the programmer to use json-encoded data while staying completely aware of when the JSON might not match the programmer's expectations, all without throwing a run-time error! This lesson will show the viewer how to turn a simple JSON object into an analogous Elm record and use it to render some content to the screen.
Instructor: [00:00] I'd like to turn a JSON string into some data today. In order to do that, I need a JSON string. I'm going to make a value here, called sample, and I'm going to give it the type of string.
[00:10] I'm going to use three double quotes, which indicates the start of a multi-line string. I'll come down here, finish it up with three other double quotes. In here, I'll start writing some JSON. There's some brackets. I'm going to give it just a couple of values.
[00:25] Let's pretend I've got a person here, represented with JSON, and they've got a name field, and maybe that name is going to be Gandalf. They're going to have an email field, which is email@example.com. I've got a double comma there. Let's take one of those commas and put it over here instead.
[00:49] The last thing Gandalf is going to have here is his favorite number. Gandalf's favorite number, I'm going to guess, is two.
[00:58] Now that I've got the sample data, I want to actually turn this into something that I can use in Elm to generate views, which is hard to do if all I've got is a string. I'm going to make a new type in here. I'm going to say type alias person equals, and I'm going to give it the same fields that I gave Gandalf below -- name, string.
[01:19] We don't want this just to represent Gandalf. We want it to represent any kind of person. Everyone that's a person, we're going to pretend they have a name and an email. I said pretend, but I actually mean we're going to mandate that they have a name, an email, and a favorite number. That favorite number is going to be a of type int.
[01:36] Here, we have an Elm type which we can pretty easily reach into and use attributes of this type, if we have data populating this type, which at the moment, we don't. Our data looks like a string. We need some way to get this string into this type.
[01:51] There is a way to do that. That's called JSON decoding, which we're going to do right now. We're going to do some JSON decoding. Let's build a person decoder.
[01:59] This is going to be an object that we can run things through in order to get data out. In other words, we're going to use this object, give it some strings, and hope that it gives us our types on the other side.
[02:11] Here, I'm going to write the type annotation for this. It's going to be a type decoder, which is right down here, json.decode.decoder. I've got to give it a type that it's decoding to, which in this case is person. We're going to make a value that's a person decoder, and has the type decoder of person.
[02:31] Here, I'm going to give it a value. What do we put in here? Great question. I'm going to introduce you to a nice, little package called elm-decode-pipeline. I'm going to install that using my editor. I'm going to go ahead and say install package.
[02:45] This is a plugin for Atom called elmjutsu, but I'm going to use it to search for the elm-decode-pipeline package from NoRedInk. There it is. I'm going to install it. Now it's installed.
[02:55] Let's go ahead and start typing. I'm going to type decode. That's a function that comes from this library. I'm going to hit enter. It's going to import it for me, my nice, little, IVE-ish plugin, it's going to import that for me, as you can see right here, import json.decode.pipeline exposing decode.
[03:11] Decode is a function that takes a constructor, a data constructor, takes a thing that takes some arguments, and puts out this type right here.
[03:19] I can go ahead and use the name of the type alias right here, because Elm automatically generates a function for us with this name that, when given the arguments of the fields here in the record, will give us record of type person. I can type decode person right here.
[03:35] Next thing we do with elm-decode-pipeline is add a new line and a pipe. This is why it's called a pipeline. That pipe is just a vertical bar, and then a right caret. The next thing I'll type is required, and make sure I'm importing the required from the JSON decoding library.
[03:48] The reason I'm using required right here is because we're going to have a required field on our JSON model, called name. In other words, we're going to say that if we're turning a string of characters into a person, we need there to be a name field on it, otherwise we don't know what to do, so we're saying required name.
[04:02] It's going to look in that string to see if it can find a name attribute inside of the object. Then it needs to know, under the name attribute, what types should I look for there. We're going to tell it, "We're going to need you to look for a JSON string." I'll type JSON string, and let my editor fill in that value for me right there, which is json.decode.string.
[04:21] Let's go ahead and add another field, which is required email. That's also a string, so we can leave that. Let's add one more field, and call it required favorite number. That is not going to be a string. It's going to be an int, which luckily, there is also an int function pre-written for us.
[04:40] We can pull that out with no problem. Let's save and see if we have any errors. No, looks like we don't have any errors. That's fine.
[04:47] I've got a decoder right here. I've got a type right here. I've got a string right here. I need to put them all together somehow. Up in my main function, all I'm doing is rendering some text to the screen, that says, "Hello."
[04:57] Instead of doing that, let's go ahead and try some decoding out. I'm going to a let block here, which lets me bind a value to a name. Let's call it decoded person. I'm going to put an equals sign. Right here, let's go ahead and do the decoding.
[05:12] Here, we can type json.decode.string, and let the plugin fill in that function for us. It expects a decoder and a string. We've got a decoder right here. It's called person decoder. We've also got a string, and it's called sample. What type is this going to return?
[05:30] Decode person is going to be of type -- we can see by hovering over right here -- result string A. You can see that right there, result string A. A result is something that can either fail with a string error -- an explanation of why it failed -- or it can succeed with the thing that you're trying to do.
[06:05] We don't like to do that, if possible. We'd like to avoid that. Let's go ahead, first of all, fix our JSON. Up here, we'll realize that this decoded person isn't just a person type, but it's actually a result, with either a person inside or a string. Let's go ahead and render that out to the screen, and see what it looks like.
[06:24] I'll make a text node here, and I'm going to use this pipe character to say everything that's on the right of this pipe, we're going to pass into the text node. I'll type to string, and use another pipe, saying everything on the right here, I'll pass it into to string. Let's pass in decoded person. Let's get rid of this old line, save the file.
[06:43] Over here in the browser, we can see that we have this thing that says OK, and then an object named Gandalf, email, favorite number, etc. That's pretty neat. That means that our decoding succeeded.
[06:55] What if we go in, and like before, throw in some gobbledygook and saved the file? We can see here an error given, an invalid JSON. Here, it's saying, "Yeah, I needed a comma here or a bracket, but all I found is a bunch of gobbledygook."
[07:08] That's pretty neat. That means that Elm is helping us to know when our JSON doesn't look as we expected it to look.
[07:14] What if we actually want to use this result? This is where pattern matching is super helpful. We can type case decoded person of, and now, it's going to expect us to tell it what to do in every possible case.
[07:29] A result only has two possible cases. Those two possible cases are OK, and then the person, and then we put an arrow right here, and we go ahead, we can define what to do with the person. I'm typing all crazy here.
[07:47] Down here, there could be an error case, err. In that case, we're going to give us a little message about what went wrong. Let's say, "Do something about failure." Now, we got to decide what to do in either circumstance.
[08:02] Right here, in the successful case, now we've got a person to handle. We can go ahead and display their name, maybe. Let's go ahead and put a div here. Inside of that div child, let's go ahead and put a h1. Inside of that h1, let's put some text that says person.
[08:21] Next to that h1, let's put a value, some text that is the person.name. I just realized I don't need to put that pipe in there, so text person.name.
[08:33] Down here, in the error case, let's go ahead and put in a nice, friendly error message, instead of spitting out that big, ugly thing before. Let's write text sorry, we're having some difficulty. Please come back later.
[08:50] Something generic that'll get people off our back, and we can send them...Oh, how about this, please contact support. Sorry, support. That's what we're doing. Save the file.
[09:00] We got some errors, because we haven't imported these things yet. I'm going to use Atom to do that as well. These plugins are very convenient. I'm going to import those things, save the file. Look at that, we've got a person. We've got a person whose name is Gandalf.
[09:13] What if we go mess up our JSON again? Let's do the infamous trailing comma right there and save the file. Sorry, we're having some difficulty. Please, come back later.
[09:22] In this case, you wouldn't actually crash the whole application. You would hopefully do something more useful to the user than just telling them to contact support. The deal is here that when you're turning a string of JSON into actual data, you have the option to recover gracefully from a failure, which is pretty neat.
[09:36] To recap, what we've done here is taken a string with JSON inside of it. We've desired to put it into a type that we can use within Elm. We wrote a decoder that matched up with the type, and told us how to take a string and turn it into the type.
[09:51] Up here, we actually used that decoder by calling decode.string. We've got our result, and in that result, we were able to either get the successful parsed data out, or we got an error out, with a message. We could either use that message, or maybe to log in to the console, or give the user some nicer, kinder prompt.
Hi, I don't know precisely since when, but in recent versions Elm has been providing some helpers which could simplify JSON decoding (at least for structures of low/average complexity).
So, if anybody is stumbling upon this lesson, I'd suggest to have a look here: https://guide.elm-lang.org/effects/json.html#combining-decoders
In fact, we could just use the Json.Decode module (https://package.elm-lang.org/packages/elm-lang/core/5.1.1/Json-Decode) and do the following:
personDecoder : Decoder Person
(field "name" string)
(field "email" string)
(field "favouriteNumber" int)
Thanks for this lesson! I am a newbie to Elm and every info is precious to me 🏅