Converting JSON with Elm
Tags: elm, functional, webdev
Published: 2019-12-10 06:32:50

_Photo by TimJ on Unsplash_

I've been getting into [Elm](elm-lang.org) in the last few weeks. I feel like I've been getting too comfortable with React and I wanted to get into something that was more [_Functional_](https://en.wikipedia.org/wiki/Functional_programming)

So far I have been enjoying my experience, the concepts that the language encourages are fascinating to me, a lot of them I was already using in Javascript and it made my code better in a way that was hard to explain to my peers.

Today I want to tackle a simple task, something that literally every web application does to a certain degree, I want to call an API and present some of the data fetched. I'm using [Random User API](https://randomuser.me) which is a free service that is consumed as REST. The final product can be found  [here](https://jdmg94.github.io/elm-user-card-app/) and the source  is linked below:

{% github jdmg94/elm-user-card-app %}


> **Disclaimer:** 
> I am still learning Elm, I don't think I'm qualified to teach anyone about it, however these are my thoughts and experience with it.

I won't be able to take advantage of **_all_** the data that this nice API provides and make some complicated profile builder, I would like to just show a card with a name, a picture, a handle maybe. But the response we get is rather extense:


```json
{
  "results": [
    {
      "gender": "female",
      "name": {
        "title": "Mrs",
        "first": "Lucy",
        "last": "Cruz"
      },
      "location": {
        "street": {
          "number": 5454,
          "name": "Fincher Rd"
        },
        "city": "Bundaberg",
        "state": "New South Wales",
        "country": "Australia",
        "postcode": 6462,
        "coordinates": {
          "latitude": "-87.1460",
          "longitude": "149.9745"
        },
        "timezone": {
          "offset": "+1:00",
          "description": "Brussels, Copenhagen, Madrid, Paris"
        }
      },
      "email": "lucy.cruz@example.com",
      "login": {
        "uuid": "92878432-b5ee-4539-92ae-b3bc44880832",
        "username": "whitepanda288",
        "password": "padres",
        "salt": "t34unJhk",
        "md5": "6bbd926216c8ba43e619b6a33c3b0fa0",
        "sha1": "f2beb504929ef81d8846d8007c2ef72f5ff8f574",
        "sha256": "d4671f10b485c7e8531d70d726e1e07be4a68eb4b75c2589262abb3f36d6aefe"
      },
      "dob": {
        "date": "1983-02-22T00:20:05.732Z",
        "age": 36
      },
      "registered": {
        "date": "2015-08-05T13:35:58.976Z",
        "age": 4
      },
      "phone": "02-5505-2105",
      "cell": "0430-474-187",
      "id": {
        "name": "TFN",
        "value": "251930914"
      },
      "picture": {
        "large": "https://randomuser.me/api/portraits/women/29.jpg",
        "medium": "https://randomuser.me/api/portraits/med/women/29.jpg",
        "thumbnail": "https://randomuser.me/api/portraits/thumb/women/29.jpg"
      },
      "nat": "AU"
    }
  ],
  "info": {
    "seed": "001e6b8cb1b5ecfa",
    "results": 1,
    "page": 1,
    "version": "1.3"
  }
}

```


In order to get what we need out of this large response object we need to understand [_decoders_](https://guide.elm-lang.org/types/type_aliases.html) and [_type aliases_](https://guide.elm-lang.org/types/type_aliases.html). Decoders are Elm's way of handling JSON data, they provide instructions on how to handle a JSON input. Type Aliases are a schema of the data we intend on getting out of this bunch. 

> **note:**
> I'll leave the boilerplate aside for all examples, however all examples should be interpreted as living in the same file.

first, let's define the data we want to receive on the view, the _ideal scenario_ from the perspective of a view function.


```elm
type alias User = { firstName : String, lastName : String, username: String,  picture : String }
```


now that we have our type alias, we can define a model, very simple:


```elm
type Model = Loading | Fail | (Success User)
```


Notice that `Success` is encapsulated with our `User` type alias, elm supports the concept of [tuples](https://guide.elm-lang.org/core_language.html#tuples) that allows us to bind 2 or 3 types as a single element, parenthesis are optional.

We'll come back to types, however, for now I want to transition to _Decoders_, since we are working on a hypothetical _ideal_ scenario with our declarations above, we need to transform our JSON data. The first thing we're going to do is to pick the fields from the first result item, where the data resides. Type aliases are also functions, and they are initialized with their paths as curried parameters.
If your schema is rather complicated you may want to look into `Json.Decode.Pipeline`



```elm
userDecoder: Decoder User
userDecoder = map4 User
  (at ["name", "first"] string)
  (at ["name", "last"] string)
  (at ["login", "username"] string)
  (at ["picture", "large"] string)
```


Notice we're declaring `userDecoder` twice, the first one is the function's _prototype declaration_ which is going to tell the compiler what to expect from this function. The latter is the function's body, and in it we want to use the `map` function to return the **4** fields we need to initialize a `User` record, remember to send them in the order of declaration.

Now that we have the `userDecoder` ready we can start thinking about how to get there from the real world, we need to make a request to the server and then unpack the results object before we can use our `userDecoder`, in order to achieve that we will write another decoder to unwrap the results from our non-existent request: 


```elm
resultsDecoder: Decoder User
resultsDecoder = field "results" index 0 userDecoder
```


This decoder is also going to return a User record, but it is going to help us unwrap the layers we don't really need before extracting our data.
In order to accomplish this we're going to use the `userDecoder` we declared a moment ago, Decoders are just functions and being able to compose them like this really helps splitting tasks like this very easy.

Alright, I think we've done well so far with our decoders, before we can make our request I need another type that will allow me to update the application's state, and I want to give my users the option to _refetch_ as well:


```elm
type Msg = anotherOne | gotUser (Result Error User)
```

One of these enumerations of type `Msg` will serve as a loading flag, another one will handle when the task is done. Notice the latter is a _touple_, that as another _touple_ as its second element. With that in place let's make our request:


```elm
getRandomProfile: Cmd Msg
getRandomProfile = Http.get {
    url = "https://randomuser.me/api",
    expect = Http.expectJson gotUser resultsDecoder
  }
```


Using the `get` method of the `Http` package, we can pass a record with 2 members, the _url_ to send the request, and what to expect from it's response, the `Http` package has other helpful functions for this, in our case we will call `expectJson` because that's what our decoders expect, and with that in place we can pull it together:


```elm
  browser.element = {
    init = init,
    view = view,
    update = update,
    subscriptions = subscriptions
    }

  init: () -> (Model, Cmd Msg)
  init _ = (Loading, getRandomProfile)
  
  subscriptions : Model -> Sub Msg
  subscriptions model = Sub.none

  update: Msg -> Model -> (Model, Cmd Msg)
  update msg model = 
    case msg of 
      AnotherOne -> (Loading, getRandomProfile)
      GotUser result -> 
        case result of 
          Ok profile -> (Success profile, Cmd.none)
          Err _ -> (Fail, Cmd.none)

  view: Model -> Html Msg
  view model = container [ 
    case model of
      Loading -> loader []
      Fail -> loader []
      Success profile -> card [ 
        onTheLeft [ roundedImage profile.picture ],
        detail [
          title (profile.firstName ++ " " ++ profile.lastName),
          subtitle ("@" ++ profile.username),
          refresh [onClick anotherOne]
          ]
        ]
    ]
```


## Conclusion


Elm has the right paradigms to succeed, but for now React's roadmap is looking brighter. The advantage that Elm has over React is that React had to steer the community towards the right patterns (i.e: functional components over class components) to make concurrency work. Elm by its functional nature is already capable of concurrency, but at this point its not taking advantage of it in the way that React is, and in the future, when React Concurrent mode is released the difference is going to be accentuated. If elm wants to compete in the frontend world concurrency is going to be fundamental to its longevity, nonetheless it is an enjoyable language with the right paradigms, I think this would be a great first language for someone getting into web development.