Elmの基本的なJSONのデコードをかなり親切に解説する

## Advent Calendar参加してます

この記事は[Elm2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/elm2)の19日目の記事です。

## はじめに

こんにちは。
突然ですが、みなさんはElmのJSONの扱いで詰まったことはありませんか。僕はElmを触り始めた時、たった1ネストのJSONをデコードするのに丸1日かかったりして泣きそうになりました。

Elmはドキュメントが充実していますが、関数の型と1つexampleが載っている程度で、手取り足取り最後まで解説してくれているわけではありません。という訳で、今回は過去の自分のようなJSONのデコードが分からない人向けに、かなり親切目に解説記事を書いてみようと思います。全部REPLで試せるコードなので、ぜひ自分のターミナルで一緒に試してみることをおすすめします。

(こんな記事を読まなくても自分でREPLとドキュメントを読めばどう書けばいいのか分かるだろ！という意見は賛成ですが、少しでも理解の助けになれば幸いだと思って書いています。また、「こうも書けるよ！」「こう書いたほうが良いよ！」といった意見は歓迎です。)

## 想定する読者

Elm Guideを読み終わったけど自分でまだJSONのデコードがよく分かってない人

## 筆者について

Elmを触って1~2ヶ月ぐらいです。Elm楽しい。

## きほんのき

まず、Jsonをデコードするためのパッケージ[`Json.Decode`](https://package.elm-lang.org/packages/elm-lang/core/5.1.1/Json-Decode)をインポートする。
`as D`とすることでそれ以降は`D`として扱える。

```elm
import Json.Decode as D
```

`Json.Decode.decodeString`を使う。

```elm
> D.decodeString 
<function> : D.Decoder a -> String -> Result D.Error a
```
これは第1引数に`Decoder`を受け取り、第2引数に(JSONの)文字列を受け取ることで第1引数の`Decoder`が指定した型の値を返す関数。具体的には以下のようにして使う。

```elm
> D.decodeString D.string """ "a" """
Ok "a" : Result D.Error String
```

`D.string`は`String`用の`Decoder`。

```elm
> D.string
<internals> : D.Decoder String
```

(実際には`decodeValue`がjsとのやり取りでよく使われるようなのだけれど一旦後述)

### Intの場合

↑のD.stringをD.intに変えるだけ。

```elm

> D.decodeString D.int """ "1" """
Ok 1 : Result D.Error Int
```

### List (List String)

`["a", "b"]`のようなリストのJSONをパースする。D.listを使う。
D.listはD.stringなどとは違って、
1. `Decoder`を引数として受け取り、
2. 返り値に引数の`Decoder`の型からなるリストの`Decoder`を返す。

```elm
> D.list
<function> : D.Decoder a -> D.Decoder (List a)
```
なので、`D.string`と組み合わせると`List String`の`Decoder`ができる。

```elm
>D.list D.string
<internals> : D.Decoder (List String)
```

ここまでくれば、`D.decodeString`の第1引数の型である`D.Decoder a`になっているので、組み合わせると`String`を`List String`にパースできる関数が作れる。

```elm
> D.decodeString (D.list D.string)
<function> : String -> Result D.Error (List String)
```

なので、これにList StringのJSONを食わせてやれば

```elm
> D.decodeString (D.list D.string) """[ "a", "b"]"""
Ok ["a","b"] : Result D.Error (List String)
```
ElmのList Stringにデコードできる。yay!

### 複数の要素を1回で取り出すデコーダ

`{i: Int, s: String}`を取り出せるデコーダを作る。 

取り出したい要素の数に応じて`mapN`を使う。

```elm 
> D.map2 
<function>  : (a -> b -> value) -> D.Decoder a -> D.Decoder b -> D.Decoder value
```
これの型定義がややこしいのだけれど、要するに

- 第1引数：最終的に生成したい型のコンストラクタ(①)
- 第2引数：(①)の第1引数の型のデコーダ
- 第3引数：(①)の第2引数の型のデコーダ

という感じ。(mapNによって最終的に生成されるのは`D.decodeString`の第1引数に与えるデコーダということを念頭に置いて考えましょう)

具体的には以下のような感じ。

まず、取り出したい要素の(カスタム)型を作る。

```elm
type alias MyRecord = { i : Int, s : String}
```

次に、map2を使ってデコーダを組み上げる。

```elm
> D.map2 MyRecord D.int D.string
<internals> : D.Decoder MyRecord
```

これが出来れば`decodeString`に渡せる。

```elm
myRecordDcoder = D.map2 MyRecord D.int D.string
> D.decodeString myRecordDcoder
<function> : String -> Result D.Error MyRecord
```

...と言いたいところなのだけれど、これでは成功しない。実際にJSONを渡すと、

```elm
> D.decodeString myRecordDcoder """ {"i": 5, "s":"two"} """
Err (Failure ("Expecting an INT") <internals>)
    : Result D.Error MyRecord
```

と、エラーになってしまう。これは`myRecordDcoder`に問題があり、「JSONのどのkeyを取得するか」という情報が抜けているためである。というわけで書き直す。

keyの指定には`D.field`を使う。

```elm
> D.field
<function> : String -> D.Decoder a -> D.Decoder a
```

これは

1. 取得したいkeyをStringで、
1. valueの型の`Decoder`

を受け取って、`Decoder a`を返す関数。

```elm
> D.field """ "i" """ D.int
<internals> : D.Decoder Int
```

なので、`map2`の`D.int`たちをこれで書き換えてやれば良い。

```elm
> myRecordDecoder2 = D.map2 MyRecord (D.field """ "i" """ D.int) (D.field """ "s" """ D.string)
<internals> : D.Decoder MyRecord
```

で、`decodeString`に渡す。

```elm
> D.decodeString myRecordDecoder2
<function> : String -> Result D.Error MyRecord

> D.decodeString myRecordDecoder2 """ {"i": 5, "s":"two"} """
Ok { i = 5, s = "two" }
    : Result D.Error MyRecord
```
できました。yay!

### ネストしたオブジェクト

上記の例から発展させて、ネストしたJSONをデコードしていく。具体的には以下のような形を想定する。

```json
{ "i" : 1,
  "s": "string",
  "data" : { 
   "imageUrl" : "https://aws.s3..." }
}
```

まず追加された要素のtype aliasを作っていく。

```elm
> type alias Data = {imageUrl : String}
<function> : String -> Data

> type alias  MyRecord = {i : Int , s: String, data : Data}
> MyRecord
<function> : Int -> String -> Data -> MyRecord
```

次に、`Data`のデコーダを作る。

```elm
> dataDecoder = 
  D.map 
    Data 
    (D.field "imageUrl" D.string)
<internals> : D.Decoder Data
```

これを、↑で作った`myRecordDecoder`に組合わせる。組み合わせる時は単純に`field`をネストさせれば良い。

```elm
> myRecordDecoder = 
  D.map3 
    MyRecord 
    (D.field "i" D.int) 
    (D.field "s" D.string) 
    (D.field "data" dataDecoder)
<internals> : D.Decoder MyRecord
```

動かしてみる。

```elm
> D.decodeString myRecordDecoder """ { "i" : 1,  "s": "string",  "data" : {"imageUrl" : "https://aws.s3..." }} """
Ok { data = { imageUrl = "https://aws.s3..." }, i = 1, s = "string" }
    : Result D.Error MyRecord
```

無事にパースできました。やったぜ。

# 参考URL

https://www.artificialworlds.net/blog/2018/10/19/elm-json-decoder-examples/


# もっと簡単なパース

NoRedInk/elm-json-decode-pipelineを使えばJSONから複数のレコードを取得するのがもっと簡単になる。

https://github.com/NoRedInk/elm-json-decode-pipeline