Elm で Google Firebase CloudStoreからデータを取得するデコーダを作ったメモ

## 概要
elmのdecoderについて、詳しい解説をしてくれているサイトはいくつもある。（[Elm  Decoder a からいろいろ理解ってしまおう](https://qiita.com/ymtszw/items/1cabbdbda4273b4c1978) / [Elm 用の CSV デコーダーを作った](http://jinjor-labo.hatenablog.com/entry/2017/07/31/225124)）
実際に、どう使うのか、が、すぐにはできなかったのでメモしておく。

## 対象とするJSON

[Cloud Firestore REST API の使用](https://firebase.google.com/docs/firestore/use-rest-api?hl=ja)を参考にして、firestoreからデータを取得する。


### コレクション
https://firestore.googleapis.com/v1/projects/garden-2a6de/databases/(default)/documents/publish/all/characters/

### ドキュメント
https://firestore.googleapis.com/v1/projects/garden-2a6de/databases/(default)/documents/characters/05TsdJiaAyAPH8RLgsqt/
## コレクションのデコード

こちらのほうがシンプル。

### 対象とするJSON

firestoreが返すjsonは以下のようなもの。

```json
{
  "documents": [
    {
      "name": "projects/garden-2a6de/databases/(default)/documents/publish/all/characters/05TsdJiaAyAPH8RLgsqt",
      "fields": {
        "labo": {
          "stringValue": "旧第一研究所"
        },
        "name": {
          "stringValue": "狐狸"
        },
        "characterId": {
          "stringValue": "05TsdJiaAyAPH8RLgsqt"
        }
      },
      "createTime": "2019-06-15T00:13:36.394340Z",
      "updateTime": "2019-06-15T00:13:36.394340Z"
    }
  ]
}
```

### デコード先のレコード
上記のJSONから以下のレコードのリストを取得したい。

```elm
type alias CharacterListItem =
    { characterId : String
    , name : String
    , labo : String
    }
```

### ネストしたオブジェクトのフィールドのデコードと配列のデコード

```json
{
  "documents": [...]
}
```

このときは、Docode.atとDecode.listを使用する。

```elm
characterListDecoder : Decoder (List CharacterListItem)
characterListDecoder =
    D.at [ "documents" ] (D.list characterListItemDecoder)
```

### オブジェクトのデコード

#### ネストしたオブジェクトのデコード

では、配列の中身のオブジェクトを見ていく。
これもまたネストしている。

```json
{
    "fields": {...},
      "createTime": "2019-06-15T00:13:36.394340Z",
      "updateTime": "2019-06-15T00:13:36.394340Z"
    }
}
```

fieldsはキャラクターのほうでも使うので、関数化してみた。

```elm
import FirestoreApi as FSApi

characterListItemFieldDecoder : Decoder CharacterListItem
characterListItemFieldDecoder =
    FSApi.fields characterListItemFieldDecoder
```

```elm:FirestoreApi.elm
fields : Decoder a -> Decoder a
fields decoder =
    D.at [ "fields" ] decoder
```

#### オブジェクトのデコード

ネストが……ネストが深い…

```json
{
        "labo": {
          "stringValue": "旧第一研究所"
        },
        "name": {
          "stringValue": "狐狸"
        },
        "characterId": {
          "stringValue": "05TsdJiaAyAPH8RLgsqt"
        }
}
```

```elm
characterListItemDecoder : Decoder CharacterListItem
characterListItemDecoder =
    D.succeed CharacterListItem
        |> required "characterId" FSApi.string
        |> required "name" FSApi.string
        |> required "labo" FSApi.string
```


```elm:FirestoreApi.elm
string : Decoder String
string =
    D.at [ "stringValue" ] D.string
```

完成！

## ドキュメントのデコード

### 対象のJSON

深い深い深い！ 実際のデータは長すぎるので省略して載せます。

```json
{
  "name": "projects/garden-2a6de/databases/(default)/documents/characters/05TsdJiaAyAPH8RLgsqt",
  "fields": {
    "kana": {
      "stringValue": "コリ"
    },
    "isPublished": {
      "booleanValue": true
    },
    "cards": {
      "arrayValue": {
        "values": [
          {
            "mapValue": {
              "fields": {
                "cardId": {
                  "stringValue": "B000"
                },
                "tags": {
                  "arrayValue": {
                    "values": [
                      {
                        "mapValue": {
                          "fields": {
                            "level": {
                              "integerValue": "0"
                            },
                            "name": {
                              "stringValue": "移動"
                            }
                          }
                        }
                      },
                      {
                        "mapValue": {
                          "fields": {
                            "name": {
                              "stringValue": "基本能力"
                            },
                            "level": {
                              "integerValue": "0"
                            }
                          }
                        }
                      }
                    ]
                  }
                },
                "maxRange": {
                  "integerValue": "0"
                },
                ...
              }
            }
          }
      }
    ]
  }
}
```

### デコード先のレコード

深くなったり長くなっている原因はレコードに色々持たせているからです。
あれも欲しいこれも欲しいとやっていると大きくなってしまう。。。


```elm
type alias Character =
    { storeUserId : String
    , characterId : String
    , name : String
    , kana : String
    , organ : String
    , trait : String
    , mutagen : String
    , cards : Array Card.CardData
    , reason : String
    , labo : String
    , memo : String
    , activePower : Int
    , isPublished : Bool
    , cardImage : String
    , cardImageData : String
    , characterImage : String
    , characterImageData : String
    , cardImageCreatorName : String
    , cardImageCreatorSite : String
    , cardImageCreatorUrl : String
    }

type alias CardData =
    { cardId : Maybe CardId
    , cardName : String
    , cardType : String
    , kind : String
    , exp : Int
    , timing : String
    , cost : Int
    , range : Int
    , maxRange : Int
    , target : String
    , maxLevel : Int
    , effect : String
    , description : String
    , tags : List Tag
    , imgMain : String
    , illustedByName : String
    , illustedByUrl : String
    , imgFrame : String
    , frameByName : String
    , frameByUrl : String
    , deleteFlag : Int
    }

type alias Tag =
    { name : String
    , level : Int
    }

```


### ネストのデコード

とりあえず、以下の構造ではある。

```json
{
    "fields" : {}
}
```

一つずつ対応していこう。

```elm
characterDecoderFromFireStoreApi : Decoder Character
characterDecoderFromFireStoreApi =
    FSApi.fields characterDecoderFromFireStoreApiHealper
```

fieldsの次は、中身。

```elm


characterDecoderFromFireStoreApiHealper : Decoder Character
characterDecoderFromFireStoreApiHealper =
    Decode.succeed Character
        |> required "storeUserId" FSApi.string
        |> required "characterId" FSApi.string
        |> required "name" FSApi.string
        |> required "kana" FSApi.string
        |> required "organ" FSApi.string
        |> optional "trait" FSApi.string ""
        |> optional "mutagen" FSApi.string ""
        |> optional "cards" (FSApi.array Card.cardDecoderFromFireStoreApi) (Array.fromList [])
        |> optional "reason" FSApi.string ""
        |> optional "labo" FSApi.string ""
        |> optional "memo" FSApi.string ""
        |> optional "activePower" FSApi.int 4
        |> optional "isPublished" FSApi.bool False
        |> optional "cardImage" FSApi.string ""
        |> optional "cardImageData" FSApi.string ""
        |> optional "characterImage" FSApi.string ""
        |> optional "characterImageData" FSApi.string ""
        |> optional "cardImageCreatorName" FSApi.string ""
        |> optional "cardImageCreatorSite" FSApi.string ""
        |> optional "cardImageCreatorUrl" FSApi.string ""
```
boolとintが欲しくなったので作ります。
Cardはちょっと別で作る。
intは決め打ちですけど、elmでintで出したものを保存している前提だからいっかなと、、

```elm:FirestoreApi.elm
int : Decoder Int
int =
    D.map (\x -> Maybe.withDefault 0 (String.toInt x)) <| D.at [ "integerValue" ] D.string

bool : Decoder Bool
bool =
    D.at [ "booleanValue" ] D.bool
```

#### 配列のデコード

配列が妙に深い、大きい気がするんですけど、elmのarrayってjsにportで渡すとマップになってるのかな。。

```json
{
    "cards": {
      "arrayValue": {
        "values": [
          {
            "mapValue": {
              "fields": {...}
           }
         ]
        }
    }          
}
```

ついでなのでリストも作っておく。

```elm:FirestoreApi.elm
array : Decoder a -> Decoder (Array a)
array decoder =
    D.at [ "arrayValue", "values" ] <| D.array (D.at [ "mapValue", "fields" ] decoder)

list : Decoder a -> Decoder (List a)
list decoder =
    D.at [ "arrayValue", "values" ] <| D.list (D.at [ "mapValue", "fields" ] decoder)

```

### カードのデコード

深い深いと思ってましたが、上記で必要なものはほぼ揃ったんですよね。
ぱっと見大きくても、一つずつ分解して考えると実はシンプル。

```elm


cardDecoderFromFireStoreApi : Decoder CardData
cardDecoderFromFireStoreApi =
    D.succeed CardData
        |> required "cardId" (D.map CardId.fromString FSApi.string)
        |> required "cardName" FSApi.string
        |> required "cardType" FSApi.string
        |> required "kind" FSApi.string
        |> required "exp" FSApi.int
        |> required "timing" FSApi.string
        |> required "cost" FSApi.int
        |> required "range" FSApi.int
        |> required "maxRange" FSApi.int
        |> required "target" FSApi.string
        |> required "maxLevel" FSApi.int
        |> required "effect" FSApi.string
        |> required "description" FSApi.string
        |> required "tags" Tag.tagsDecoderFromFireStoreApi
        |> required "imgMain" FSApi.string
        |> required "illustedByName" FSApi.string
        |> required "illustedByUrl" FSApi.string
        |> required "imgFrame" FSApi.string
        |> required "frameByName" FSApi.string
        |> required "frameByUrl" FSApi.string
        |> required "deleteFlag" FSApi.int
```

### タグのデコード

項目が少ないのでパイプラインなしです。 

```elm
tagDecoderFromFireStoreApi : Decoder Tag
tagDecoderFromFireStoreApi =
    D.map2 Tag
        (D.field "name" FSApi.string)
        (D.field "level" FSApi.int)
```

## できたもの

以下で動作しています。

https://garden-2a6de.firebaseapp.com/characters

[この時点のソース](https://github.com/hibohiboo/garden/tree/bbfe1409e5b9c1f4355894bcb4b280d40dda7523)  


## 参考

[Elm  Decoder a からいろいろ理解ってしまおう](https://qiita.com/ymtszw/items/1cabbdbda4273b4c1978)
[Elm 用の CSV デコーダーを作った](http://jinjor-labo.hatenablog.com/entry/2017/07/31/225124)
[elm/json](https://github.com/elm/json)
[elm-decode-pipeline](https://package.elm-lang.org/packages/NoRedInk/elm-decode-pipeline/latest/)
[Firebaseで保存したDictionary(連想配列)なJSONをElmでテーブル表示してみた](https://qiita.com/niso1985/items/825fd4adaf636f10b38c)
[Firebase Cloud Functions + Firestore超入門](https://the2g.com/2877)
[Cloud Firestore REST API の使用](https://firebase.google.com/docs/firestore/use-rest-api?hl=ja)
[Elmで冗長な書き方をしていたデコーダなどをリファクタリングしたメモ](https://qiita.com/hibohiboo/items/e1c4b3e47a3fb348d35c)
[arowM/elm-form-decoderでフォームを作ろう](https://qiita.com/miyamo_madoka/items/d02f003ec1c212360111)