Gsonでリストを持つジェネリックなMapをデシリアライズする

Gsonで以下のようなJSONをMapと配列的なもののセット、つまり`Map<String, Array<Nanika>>`的なものにデシリアライズしたいという話です。なお、ここでは言語としてKotlinを使っています。

```kotlin
class Nanika(var value: String? = null) {}
```

```json
{
   "0": [ { "value": "Hauhau" }, { "value": "Maumau" } ],
   "1": [ { "value": "Hello" }, { "value": "Konnichiwa" } ]
}
```

# まとめ
- Gsonに渡す型は `TypeToken<T>` を使ってジェネリックなパラメータ情報を受け渡す
- デシリアライズの型は `Map<String, Collection<T>>` を使う (Collectionである必要がある, ArrayやListではだめ)
    - Arrayの場合は謎のException
    - Listの場合にはLinkedTreeMapになってしまう

# C# (.NET) の場合
まず書き慣れたC#で書いてみます。大抵JSON.NETを使って`JsonConvert.DeserializeObject`に`Dictionary<String, Nanika[]>`を指定すれば出来上がりです。

```csharp
class Nanika
{
    public string Value { get; set; }
}

void Main()
{
    var json = @"
    {
       ""0"": [ { ""value"": ""Hauhau"" }, { ""value"": ""Maumau"" } ],
       ""1"": [ { ""value"": ""Hello"" }, { ""value"": ""Konnichiwa"" } ]
    }
    ";

    JsonConvert.DeserializeObject<Dictionary<string, Nanika[]>>(json).Dump(); // Nanika[]
    JsonConvert.DeserializeObject<Dictionary<string, List<Nanika>>>(json).Dump(); // List<T> でも IList<T> でも OK
}
```

# Gsonでデシリアライズするには
とりあえずまずは思いついたまま書いてみましょう。

Gsonでデシリアライズするには`fromJson`メソッドを使います。`fromJson`メソッドは第一引数にJSON文字列、第二引数にデシリアライズ後の型を受け取ります。

```kotlin
class Nanika(var value: String? = null) {}

val json = """
{
   "0": [ { "value": "Hauhau" }, { "value": "Maumau" } ],
   "1": [ { "value": "Hello" }, { "value": "Konnichiwa" } ]
}
"""
// Error: Only classes are allowed on the left hand side of a class literal
val retval = Gson().fromJson(json, Map<String, Array<Nanika>>::class.java)
```

まずコンパイルが通りません…。そもそもKotlinやJavaでC#で言うクローズジェネリックの型を直接引き出せないのです。
つまり `List::class.java` は書けても `List<Nanika>::class.java` は書けないということを意味します(多分この辺はType Erasureの影響の予感がします)。

> **Note**: ちなみに `List<Nanika>::class.java` は書けませんが、配列である `Array<Nanika>::class.java` は書けます

## ジェネリックな型を渡す
一旦もっとシンプルな `Nanika` 型のリスト的なものをデシリアライズしてみます。

```kotlin
val json = """
[ { "value": "Hello" }, { "value": "Konnichiwa" } ]
"""
// Error: Only classes are allowed on the left hand side of a class literal
val retval = Gson().fromJson(json, Array<Nanika>::class.java)
```

上記の通りこの書き方はできないのですが、Gsonには `TypeToken<T>` という型パラメータを保持したものを渡すための補助クラスがあるのでそれを利用します。

`TypeToken<T>` はそれを継承した無名クラスとインスタンスを作って、そこから `type` プロパティ(`getType`メソッド)を経由して型情報を取得できます。その型情報を `fromJson` メソッドに渡すことでGsonでデシリアライズ可能になります。

```kotlin
val json = """
[ { "value": "Hello" }, { "value": "Konnichiwa" } ]
"""
val typeToken = object : TypeToken<Array<Nanika>>() {} // protected なコンストラクタになっているので継承が必須
val retval = Gson().fromJson<Array<Nanika>>(json, typeToken.type) // => [ Nanika, Nanika ]
```

> **Note:** ここでは `Array<T>` を使用しましたが、`List<T>` を指定しても同様にデシリアライズできます。

## ということは
先ほどの `TypeToken<T>` を使って最初の例を書き換えてあげれば良さそうな気がします。

```kotlin
val json = """
{
   "0": [ { "value": "Hauhau" }, { "value": "Maumau" } ],
   "1": [ { "value": "Hello" }, { "value": "Konnichiwa" } ]
}
"""
val typeToken = object : TypeToken<Map<String, Array<Nanika>>>() {}
val retval = Gson().fromJson<Map<String, Array<Nanika>>>(json, typeToken.type)
```

こんな感じですね。そしてこれを実行するとどうなるかというと…

```
java.lang.RuntimeException: Unable to invoke no-args constructor for ? extends example.com.myapplication.Nanika[]. Register an InstanceCreator with Gson for this type may fix this problem.
```

なんと例外。よくわかりませんが `Array<Nanika>` (`Nanika[]` 型)をデシリアライズするときに失敗するようです。単純な配列の時はできていたのにも関わらずネストされたジェネリックな型の場合にはダメなようです。

それならば `Array<T>` の代わりに `List<T>` にしたらどうか?ということでこちらを試してみます。

```kotlin
val json = """
{
   "0": [ { "value": "Hauhau" }, { "value": "Maumau" } ],
   "1": [ { "value": "Hello" }, { "value": "Konnichiwa" } ]
}
"""
val typeToken = object : TypeToken<Map<String, List<Nanika>>>() {}
val retval = Gson().fromJson<Map<String, List<Nanika>>>(json, typeToken.type)
```

これを実行すると無事すんなりとデシリアライズするコードは通ります。

が、よかったよかった…とはならず、実際に `retval["0"]!![0]` などとして値を取り出そうとするとおかしいことに気づきます。

```
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to Nanika
```

また例外です。今度は `LinkedTreeMap` を `Nanika` にキャストできなかったという内容です。これはAndroid Studioで変数の中身をのぞくとわかるのですが `LinkedTreeMap<String, ArrayList<LinkedTreeMap>>` になってます。なんでやねん。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/50267/889e89f6-385b-6e50-6a69-5d73699ae011.png)

## 結局どうするのか

デシリアライズ先の型のネストされたジェネリック型を `Array<T>` でもなく `List<T>` でもなく `ArrayList<T>` でもなく **`Collection<T>`** にします。

```kotlin
val json = """
{
   "0": [ { "value": "Hauhau" }, { "value": "Maumau" } ],
   "1": [ { "value": "Hello" }, { "value": "Konnichiwa" } ]
}
"""
val typeToken = object : TypeToken<Map<String, Collection<Nanika>>>() {}
val retval = Gson().fromJson<Map<String, Collection<Nanika>>>(json, typeToken.type)
```

これで無事デシリアライズでき、内容を取り出せるようになります。

> **Note:** ちなみに `Collection<T>` だとインデックスアクセスできないのですが、実際デシリアライズ後は `ArrayList<T>` なので `fromJson` の型パラメータを雑に `Map<String, ArrayList<Nanika>>` にすれば受け取れます