A beginner’s guide to JSON and Elm

Mario Martinez
6 min readJun 26, 2016

--

In this tutorial, using Elm 0.17, we’re going to make simple app that finds and displays movie posters. You can see a demo of what we’re going to build here. [EDIT: due to OMDb requiring API keys, the above app is no longer working.]

To follow this guide, you will need to have installed Elm on your computer. It would also be beneficial if you have had exposure to something like the buttons tutorial so you have an idea of how an Elm app works.

Let’s get started by creating a folder somewhere on your computer called movie_poster_finder. Open your newly created folder, create a file called Main.elm and open it with your favourite text editor. The above steps in the command line would look like this if you were using Atom as your text editor:

$ mkdir movie_poster_finder$ cd movie_poster_finder$ touch Main.elm$ atom .

To get things started, enter the following into Main.elm. This is just the starting point and we’re not making any API calls or decoding JSON.

There’s an image called waiting.gif. You can download the image here and add it to the movie_poster_finder folder.

To see the above bit of code in action, using your terminal, from within the movie_poster_finder folder, run the command elm-reactor.

With elm reactor running, visit http://localhost:8000/Main.elm in your browser. You should be presented with this error:

Downloading elm-lang/core
Packages configured successfully!
I cannot find module ‘Html’.
Module ‘Main’ is trying to import it.Potential problems could be:
* Misspelled the module name
* Need to add a source directory or new dependency to elm-package.json

Elm’s friendly compiler is telling us that we need to add the Html module. That’s an easy fix. If you go into your movie_poster_finder folder, you’ll see a new file that was created when we ran elm-reactor called elm-package.json. This file has the package information about our app. We’ll need to add information about the Html module to this file so that the Elm compiler knows where to find it. To save a bit of time, we’ll add all the other dependencies we’ll be needing for this project. Update your elm-package.json as follows:

{
"version": "1.0.0",
"summary": "helpful summary of your project, less than 80 characters",
"repository": "https://github.com/user/project.git",
"license": "BSD3",
"source-directories": [
"."
],
"exposed-modules": [],
"dependencies": {
"elm-lang/core": "4.0.1 <= v < 5.0.0",
"elm-lang/html": "1.0.0 <= v < 2.0.0",
"evancz/elm-http": "3.0.1 <= v < 4.0.0",
"elm-community/json-extra": "1.0.0 <= v < 2.0.0",
"NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0"
},
"elm-version": "0.17.0 <= v < 0.18.0"
}

Now, refresh http://localhost:8000/Main.elm and you should see something like this:

You can type in your favourite movie title but don’t expect much to happen.

So far, our app doesn’t do much but it’s a good foundation for what we’re about to accomplish.

To get our movie poster data, we’re going to use the Open Movie Database (OMDB). OMDB is great for educational projects like this.

Remember how we added those dependencies to elm-package.json? Let’s import them into to our Main.elm file. Update the import list at the top of Main.elm to look like this:

import Html exposing (..)
import Html.App as Html
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Http
import Json.Decode as Json exposing(string)
import Json.Decode.Pipeline as JsonPipeline exposing (decode, required)
import Task

Let’s go over these modules. Firstly, we have Http. That’s going to let us make http requests to OMDB and get our movie information in JSON format. Once we receive our JSON response, we need to decode it so that our Elm program can use that information. That’s what Json.Decode and Json.Decode.Pipeline are for. Lastly, we have imported Task. In Elm, when we make an http request, we use a task so that we can tell Elm what to do if something goes wrong with the http request.

See, that’s one of the great things about Elm: it forces us to handle the error case!

Since we’ve imported all those modules, let’s put them to use. Beneath subscriptions in Main.elm, add the following lines of code.

-- HTTP
getMoviePoster : String -> Cmd Msg
getMoviePoster searchString =
let
url =
"//www.omdbapi.com/?t=" ++ searchString
in
Task.perform FetchFail FetchSucceed (Http.get decodeMovieUrl url)
type alias Movie =
{ title : String
, posterUrl : String
}
decodeMovieUrl : Json.Decoder Movie
decodeMovieUrl =
decode Movie
|> JsonPipeline.required "Title" string
|> JsonPipeline.required "Poster" string

getMoviePoster is the function that makes the API call to OMDB. Notice that we have two messages: FetchFail and FetchSucceed. We’ll talk more about these later.

Movie is the record that we want to use when we decode the JSON response. It is a type alias because we’re not creating a new type but just giving a record a more meaningful name.

This brings us to decodeMovieUrl. Since Elm is a typed language, we need to ensure that whatever we get from OMDB matches the type that we expect.

With the aid of elm-decode-pipeline (imported as Json.Decode.Pipeline), decoding JSON is as easy as stating that the key is required (as opposed to optional), stating the JSON key (eg: “Title”) and the type we expect (eg: string).

Now that we have a way to make http requests and decode the JSON response, let’s update the view.

-- VIEWview : Model -> Html Msg
view model =
div []
[ input [ placeholder "enter a movie title"
, value model.searchString
, autofocus True
, onInput UpdateSearchString
] []
, button [ onClick GetPoster ] [ text "Get poster!" ]
, br [] []
, h1 [] [ text model.title ]
, img [ src model.posterUrl ] []
]

Next up, we have the update function. If you’ve been keeping count, we have four messages to deal with: GetPoster, FetchSucceed, FetchFail and UpdateSearchString. So the update part of our app will look like this:

-- UPDATEtype Msg
= GetPoster
| FetchSucceed Movie
| FetchFail Http.Error
| UpdateSearchString String
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
GetPoster ->
{ model | posterUrl = "waiting.gif"
, title = ""
} ! [getMoviePoster model.searchString]
FetchSucceed movie ->
(Model model.searchString movie.title movie.posterUrl, Cmd.none)
FetchFail error ->
let
errorMessage = "We couldn’t find that movie 😯"
errorImage = "oh-no.jpeg"
in
(Model model.searchString errorMessage errorImage, Cmd.none)
UpdateSearchString newSearchString ->
{ model | searchString = newSearchString } ! []

We’re making great progress! Let’s talk about these four messages.

Firstly, we have GetPoster. This makes the API call. We set the poster URL to our loading image (waiting.gif) and set the title to an empty string. This puts our app in the “loading… please wait” state. We also call the function getMoviePoster which, as we discussed earlier, makes the API call to OMDB.

Should the above succeed, we get the message FetchSuceed. We can update the model with the movie title and the movie poster URL. However, if it fails, we have FetchFail. We replace the title with an error message and an error image. For an error image, I used the one below. Be sure to add an oh-no.jpg to your movie_poster_finder folder.

oh-no.jpg

One more tiny bit and we’re done. We need to update the way we initialise our model.

init : (Model, Cmd Msg)
init =
( Model "Frozen" "" ""
, getMoviePoster "Frozen"
)

Without the above change, when we start our app, we wouldn’t get a poster image.

To observe your handy work, visit http://localhost:8000/Main.elm and reload the page. You can see the complete code here.

And that’s it! Well done 😀 I hope you learned something from this tutorial.

--

--