【Unity(C#)】WebAPIで返ってきたJSONデータの扱いでつまったところ

##ネストしたJSON、ルートが配列のJSON

WebAPIを利用した時にネストしたJSON、ルートが配列のJSONが返ってきて、
その扱い方がわからず調べたのでメモしときます。

例として[Microsoft Tlanslator](https://docs.microsoft.com/en-us/azure/cognitive-services/translator/translator-info-overview)のAPIを利用した際のレスポンスを想定して話を進めます。

##ネスト

まずはネストについてです。入れ子になっている構造のことを指します。
下記のようなネストしたJSONがレスポンスBodyとして返ってきたと想定します。

```json:レスポンス
    {
        "detectedLanguage": {
            "language": "en",
            "score": 1.0
        },
        "translations": [
            {
                "text": "Hello",
                "to": "en"
            },
            {
                "text": "こんにちは",
                "to": "ja"
            }
        ]
    }
```

## コード

早速ですがコードです。

```C#
using System;
using UnityEngine;

/// <summary>
/// ネストしたJSONが返ってくる想定
/// 各KeyからValueを取得するサンプル
/// </summary>
public class JsonSample : MonoBehaviour
{
    [Serializable]
    public struct TranslateData
    {
        public DetectedLanguage detectedLanguage;
        public Translations[] translations;

        [Serializable]
        public struct DetectedLanguage
        {
            public string language;
            public string score;
        }

        [Serializable]
        public struct Translations
        {
            public string text;
            public string to;
        }
    }

    void Start()
    {
        //今回は実際のレスポンスではなくJSONデータのロードで検証
        var json = Resources.Load<TextAsset>("sample").ToString();
        var data = JsonUtility.FromJson<TranslateData>(json);

        //各Valueをログに吐き出す
        Debug.Log(data.detectedLanguage.language);
        Debug.Log(data.detectedLanguage.score);
        Debug.Log(data.translations[0].text);
        Debug.Log(data.translations[0].to);
        Debug.Log(data.translations[1].text);
        Debug.Log(data.translations[1].to);
    }
}
```

TranslateDataという構造体の中にネストして構造体を定義しています。
JSONデータと似たような作りになるので見やすいかと思います。

```C#
    [Serializable]
    public struct TranslateData
    {
        public DetectedLanguage detectedLanguage;
        public Translations[] translations;

        [Serializable]
        public struct DetectedLanguage
        {
            public string language;
            public string score;
        }

        [Serializable]
        public struct Translations
        {
            public string text;
            public string to;
        }
    }
```

## つまったとこ

知っていれば大したことないですが、初見で時間を使ってしまった点をメモに残します。

---

### JSONのKEYと構造体の変数名

JSONのKEYと定義した構造体の変数名が一致している必要があります。
不一致の場合はNullが返ってきました。

---

### 配列及びリストからオブジェクトを作成

配列及びリストからオブジェクトを作成する際に、
ルートのオブジェクトが配列やリストにならないようにする必要があるようです。
例えば下記のようなJSONデータがあったとします。

```json

{
    "translations": [
        {
            "text": "Hello",
            "to": "en"
        },
        {
            "text": "こんにちは",
            "to": "ja"
        }
    ]
}
```

こちらを下記のように配列のままJsonUtility.FromJsonのジェネリクスに渡して変換し、
Valueを取り出そうとするとエラーとなります。

```C#
    [Serializable]
    public struct Translations
    {
        public string text;
        public string to;
    }
    
    void Start()
    {
        var json = Resources.Load<TextAsset>("sample").ToString();
        var data = JsonUtility.FromJson<Translations[]>(json);
    }
```

一つ構造体を定義し、その中で配列を宣言すればOKです。

```C#
    //配列をラップ
    [Serializable]
    public struct TranslateData
    {
        public Translations[] translations;

        [Serializable]
        public struct Translations
        {
            public string text;
            public string to;
        }
    }
    
    void Start()
    {
        var json = Resources.Load<TextAsset>("sample").ToString();
        var data = JsonUtility.FromJson<TranslateData>(json);
    }
```

---

### JSONのルートが配列の場合

最初のJSONデータの例では挙げませんでしたが、
Microsoft Tlanslatorのレスポンスとして返ってくるJSONデータはルートが配列で返ってきます。

```json:実際にI_love_youでリクエスト投げた際のレスポンス
[
    {
        "detectedLanguage": {
            "language": "en",
            "score": 1.0
        },
        "translations": [
            {
                "text": "愛しています",
                "to": "ja"
            },
            {
                "text": "I love you",
                "to": "en"
            }
        ]
    }
]
```

`[`で囲われているのがわかります。すなわちルートが配列ということです。
これをそのまま利用してオブジェクトを作成することはできません。

解決策として2つのアプローチを試しました。

まず、配列の上に新たにKeyを定義して配列をルートの一階層下にする方法です。

```C#
using System;
using UnityEngine;

/// <summary>
/// ネストしたJSONが返ってくる想定
/// 各KeyからValueを取得するサンプル
/// </summary>
public class JsonSample : MonoBehaviour
{
    //TranslateDataが新たにルートになる
    [Serializable]
     public struct TranslateData
     {
         //レスポンスのルートをここに入れる
         public Root[] root;
         
         [Serializable]
         public struct Root
         {
             public Translations[] translations;
         }

         [Serializable]
         public struct Translations
         {
             public string text;
             public string to;
         }
     }
     

    void Start()
    {
        //今回は実際のレスポンスではなくJSONデータのロードで検証
        var rawJson = Resources.Load<TextAsset>("sample").ToString();
        var json = "{" + $"\"root\":{rawJson}" + "}";
        var data = JsonUtility.FromJson<TranslateData>(json);
        
        //各Valueをログに吐き出す
        Debug.Log(data.root[0].translations[0].text);
        Debug.Log(data.root[0].translations[0].to);
        Debug.Log(data.root[0].translations[1].text);
        Debug.Log(data.root[0].translations[1].to);
    }
}
```

次に、`[`を消してしまう方法です。
最初と最後の文字列を消すというごり押しです。

```C#
using System;
using UnityEngine;

/// <summary>
/// ネストしたJSONが返ってくる想定
/// 各KeyからValueを取得するサンプル
/// </summary>
public class JsonSample : MonoBehaviour
{
    [Serializable]
    public struct TranslateData
    {
        public Translations[] translations;

        [Serializable]
        public struct Translations
        {
            public string text;
            public string to;
        }
    }

    void Start()
    {
        //今回は実際のレスポンスではなくJSONデータのロードで検証
        var rawJson = Resources.Load<TextAsset>("sample").ToString();
        var json = rawJson.Substring(1, rawJson.Length-2);
        var data = JsonUtility.FromJson<TranslateData>(json);

        //各Valueをログに吐き出す
        Debug.Log(data.translations[0].text);
        Debug.Log(data.translations[0].to);
        Debug.Log(data.translations[1].text);
        Debug.Log(data.translations[1].to);
    }
}
```

##おわりに
割と似たような現象に何度も遭遇するようなので、
ヘルパークラスなるものを定義した方が良いかもしれません。

## 参考リンク

[How to load an array with JsonUtility?](https://forum.unity.com/threads/how-to-load-an-array-with-jsonutility.375735/)
[【Unity】JsonUtilityでルート配列のデシリアライズ](https://tk3118.hatenablog.com/entry/2020/03/13/172810)
[はなちるのマイノート](https://www.hanachiru-blog.com/entry/2019/04/24/211642)
[JSONについて調べてみた](https://qiita.com/chihiro/items/dd1912c1406dbfe16b72)