Skip to content

Instantly share code, notes, and snippets.

@bytezen
Forked from yang-wei/decode.md
Created September 2, 2017 14:28
Show Gist options
  • Select an option

  • Save bytezen/74bb1bcf01547f36de886a5dec43d2cc to your computer and use it in GitHub Desktop.

Select an option

Save bytezen/74bb1bcf01547f36de886a5dec43d2cc to your computer and use it in GitHub Desktop.

Revisions

  1. @yang-wei yang-wei revised this gist Mar 1, 2016. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions decode.md
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    When receiving JSON data from other resources(server API etc), we need Json.Decoder to convert the JSON values into Elm values. This cheatsheet is to let you quickly learn how to do that.
    When receiving JSON data from other resources(server API etc), we need [Json.Decode](http://package.elm-lang.org/packages/elm-lang/core/3.0.0/Json-Decode) to convert the JSON values into Elm values. This gist let you quickly learn how to do that.

    To show you working code, this is how the boilerplate will look like:
    I like to follow working example code so this is how the boilerplate will look like:

    ```elm
    import Graphics.Element exposing (Element, show)
  2. @yang-wei yang-wei renamed this gist Mar 1, 2016. 1 changed file with 0 additions and 2 deletions.
    2 changes: 0 additions & 2 deletions decoder.md → decode.md
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,3 @@
    # Json.Decoder

    When receiving JSON data from other resources(server API etc), we need Json.Decoder to convert the JSON values into Elm values. This cheatsheet is to let you quickly learn how to do that.

    To show you working code, this is how the boilerplate will look like:
  3. @yang-wei yang-wei created this gist Mar 1, 2016.
    355 changes: 355 additions & 0 deletions decoder.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,355 @@
    # Json.Decoder

    When receiving JSON data from other resources(server API etc), we need Json.Decoder to convert the JSON values into Elm values. This cheatsheet is to let you quickly learn how to do that.

    To show you working code, this is how the boilerplate will look like:

    ```elm
    import Graphics.Element exposing (Element, show)
    import Task exposing (Task, andThen)
    import Json.Decode exposing (Decoder, int, string, object3, (:=))

    import Http

    {- declare data type here -}
    type alias SimpleRecord =
    { name : String
    , description : String
    }

    -- initialize mailbox with the type declared above
    mailbox =
    Signal.mailbox (SimpleRecord "" "")

    -- VIEW

    main : Signal Element
    main =
    Signal.map show mailbox.signal

    -- TASK

    fetchApi =
    Http.get decoder api

    handleResponse data =
    Signal.send mailbox.address data

    -- decoder changes depends on our data type
    decoder = ...

    port run : Task Http.Error ()
    port run =
    fetchApi `andThen` handleResponse

    api =
    "http://some-api-url.com"
    ```

    So here we have a mailbox - `mailbox` which contains initial data depending on the data type. When the application starts, `fetch` is called and the response is handled by `handleResponse`. Our `main` function will display the value of `mailbox` value.
    Here, we will fetch data github api and display it on screen. So to follow along this tutorial, you have to modify

    1. data type (currently SimpleRecord)
    2. initial data in `mailbox`
    3. decoder function
    4. api (to get different data)

    Notice that I am omitting some data annotations for brevity. Our main focus in this post will be `decoder` function. We will see how to utilize the Json.Decoder library when dealing with different type of data.

    ## Objects

    ### Object -> tuple

    First of all let's fetch the elm-lang/core repository. The API looks like:
    ```
    https://api.github.com/repos/elm-lang/core
    ```

    If you paste this on browser, you will see something like:
    ```json
    {
    "id": 25231002,
    "name": "core",
    ...
    "subscribers_count": 47
    }
    ```

    Let's say we are interested in 3 fields - `name`, `description` and `watchers_count` of this repository and we want to display it in simple tuple: ie (name, description, watchers_count).

    Here is how our data type looks like:
    ```elm
    type alias RepoTuple = ( String, String, Int )

    mailbox =
    Signal.mailbox ("", "", 0)
    ```
    We also need to declare the initial data in mailbox - ("", "", 0).

    Because our API returns a JSON object, the Object fields in [doc](http://package.elm-lang.org/packages/elm-lang/core/3.0.0/Json-Decode#) will give us what we need. In our case we need 3 fields, so we will use `object3`. Elm supports `object1` to `object8` and we have to decide depends on how many value we need.

    ```elm
    object3
    : (a -> b -> c -> value)
    -> Decoder a
    -> Decoder b
    -> Decoder c
    -> Decoder value
    ```

    The first argument `(a -> b -> c -> value)` is a function which takes 3 arguments and return a `value`. In Elm, we know that

    ```elm
    (,,) "Name" "Description" 100
    -- ("Name","Description",100) : ( String, String, number )
    ```

    So our decoder would be:
    ```elm
    repoTupleDecoder : Decoder RepoTuple
    repoTupleDecoder =
    object3 (,,)
    ("name" := string)
    ("description" := string)
    ("watchers_count" := int)
    ```

    The `:=` operator is use to extract the field with the given name. Since we are interested in name, description and watchers_count so we declare it explicitly and state it's type.

    ```elm
    fetchApi =
    Http.get repoTupleDecoder api
    ```

    And this is the [working code](https://gist.github.com/yang-wei/2d1c33b9c114fcd652ff#file-repotuple-elm) for this example.

    ### Object -> record

    Often tuple doesn't give enough information, in most case we want to preserve the value of field itself. So our type will look like:

    ```elm
    type alias RepoRecord =
    { name : String
    , description : String
    , watchers_count : Int
    }

    mailbox =
    Signal.mailbox (RepoRecord "" "" 0)
    ```

    Note that in this case our data type is Record so we can initialize it by `RepoRecord "" "" 0` which will return `{ name = "", description = "", watchers_count = 0 }` which is cool. What cooler is we can even reuse it in our decoder:

    ```elm
    repoRecordDecoder : Decoder RepoRecord
    repoRecordDecoder =
    object3 RepoRecord
    ("name" := string)
    ("description" := string)
    ("watchers_count" := int)
    ```

    Do remember to change the function name in `fetchApi`:
    ```elm
    fetchApi =
    Http.get repoRecordDecoder api
    ```

    Now we will get a nice record:
    ```elm
    { name = "core", description = "Elm's core libraries", watchers_count = 338 }
    ```

    Again, for your reference the source is [here](https://gist.github.com/yang-wei/2d1c33b9c114fcd652ff#file-repotuple-elm).

    ### Nested Object

    When we hit the [API](https://api.github.com/repos/elm-lang/core), it returns an object which contains the owner of repository in a nested object.

    ```json
    {
    ...
    "full_name": "elm-lang/core",
    "owner": {
    "login": "elm-lang",
    "id": 4359353,
    "avatar_url": "https://avatars.githubusercontent.com/u/4359353?v=3"
    ...
    },
    ...
    }
    ```

    Let's look at how we can retrive the value in **owner** object. We can of course create nested decoder by using 2 times of `object3` but we can avoid this by using `[at](http://package.elm-lang.org/packages/elm-lang/core/3.0.0/Json-Decode#at)`

    ```elm
    at : List String -> Decoder a -> Decoder a
    ```

    Changing everything we need:

    ```elm
    type alias OwnerRecord =
    { login : String
    , id : Int
    , avatar_url : String
    }

    mailbox =
    Signal.mailbox (OwnerRecord "" -1 "")
    ```

    Because the data we need have only single layer of nested field, so the first argument passed to `at` only has one value, that is `["owner"]`.

    ```elm
    ownerDecoder : Decoder OwnerRecord
    ownerDecoder =
    let
    decoder = object3 OwnerRecord
    ("login" := string)
    ("id" := int)
    ("avatar_url" := string)
    in
    at ["owner"] decoder
    ```


    ### Any object -> List (Tuple)

    Let's hit another API.

    ```
    https://api.github.com/repos/elm-lang/elm-lang.org/languages
    ```

    and it's result is:
    ```json
    {
    "Elm": 400423,
    "JavaScript": 352902,
    "CSS": 75013,
    "Haskell": 28719,
    "HTML": 965
    }
    ```

    In this case, we want all values.

    ```elm
    type alias Languages =
    List (String, Int)

    mailbox =
    Signal.mailbox []
    ```

    Elm provides a very handy function - `keyValuePairs`

    ```elm
    import import Json.Decode exposing (..., keyValuePairs)

    ;; ...

    decoder : Decoder (List (String, Int))
    decoder =
    keyValuePairs int
    ```

    And we have all languages in tuple:

    ```elm
    [ ("HTML", 965)
    , ("Haskell", 28719)
    , ("CSS", 75013)
    , ("JavaScript", 352902)
    , ("Elm", 400423)
    ]
    ```

    ### Any object -> Dict

    Besides `keyValuePairs`, we also has `[Decoder.dict](http://package.elm-lang.org/packages/elm-lang/core/3.0.0/Json-Decode#dict)` to turn object into dictionary.

    ```elm
    type alias Languages =
    Dict String Int

    mailbox =
    Signal.mailbox Dict.empty

    decoder : Decoder (Dict String Int)
    decoder =
    dict int
    ```

    ## Arrays

    ### Array -> tuple
    In the previous section, we had seen multiples way to deal with object typed JSON value. However sometimes, we have an array. For example:

    ```
    https://api.github.com/search/repositories?q=language:elm&sort=starts&language=elm
    ```

    This API returns repositories written in Elm.

    ```json
    "total_count": 1816,
    "incomplete_results": false,
    "items": [
    {
    "id": 4475362,
    "name": "elm-lang.org",
    "full_name": "elm-lang/elm-lang.org",
    ...
    },
    {
    "id": 25231002,
    "name": "core",
    "full_name": "elm-lang/core",
    ...
    }
    ]
    ```

    The field we are particularly interested in is the items field. Let's say we want a list which contains the `full_name` of the Elm repository.

    ```elm
    ["elm-lang/elm-lang.org", "elm-lang/core" ...]
    ```

    Let's initialize our data first:

    ```elm
    mailbox =
    Signal.mailbox []
    ```

    First of all, here is our decoder to extract the `full_name` value
    ```elm
    fullNameDecoder : Decoder String
    fullNameDecoder =
    object1 identity ("full_name" := string)
    ```

    Because our data is nested in the `items` field, we have to access it using the `at` operator (hope you still remember):

    ```elm
    decoder =
    at ["items"] _
    ```

    The `items` will give us an array which contains object, so we will use `[Decoder.list](http://package.elm-lang.org/packages/elm-lang/core/3.0.0/Json-Decode#list)`:

    ```elm
    list : Decoder a -> Decoder (List a)
    ```

    `Decoder.list` takes a decoder and returns another decoder which can handle list. This suits our case:

    ```
    decoder =
    at ["items"] (list fullNameDecoder)
    ```

    Now if you wish to also extract other fields, you just have to change your `fullNameDecoder`. The source of this example is shown [here](https://gist.github.com/yang-wei/2d1c33b9c114fcd652ff#file-listdecoder-elm).