UnityでJSONを読み書きしようぜ（特にDictionaryで）

Unity で JSON を読み書きしたい。

「How parse Json data in Unity in C#?」
https://forum.unity3d.com/threads/how-parse-json-data-in-unity-in-c.383804/

「JsonUtility.FromJson」
https://docs.unity3d.com/ScriptReference/JsonUtility.FromJson.html

公式を読むと　サンプルが載っている。

```C#
using UnityEngine;

[System.Serializable]
public class PlayerInfo
{
    public string name;
    public int lives;
    public float health;

    public static PlayerInfo CreateFromJSON(string jsonString)
    {
        return JsonUtility.FromJson<PlayerInfo>(jsonString);
    }

    // Given JSON input:
    // {"name":"Dr Charles","lives":3,"health":0.8}
    // this example will return a PlayerInfo object with
    // name == "Dr Charles", lives == 3, and health == 0.8f.
}
```

こんなクラスを作成して、

Assets/Editor/ToolMenu.cs

```C#
using UnityEditor;
using UnityEngine;

public class ToolMenu {

    #region JSONの動作テスト
    [MenuItem("Tool/Json Test")]
    static void JsonTest()
    {
        PlayerInfo pi = PlayerInfo.CreateFromJSON("{\"name\":\"Dr Charles\", \"lives\":3,\"health\":0.8}");
        Debug.Log("pi.name = " + pi.name);
        Debug.Log("pi.lives = " + pi.lives);
        Debug.Log("pi.health = " + pi.health);
    }
    #endregion
}
```

こんな感じで実行したらいいのか？

[Tool] - [Json Test] と進んで実行してみる。

![20170606e1b.png](https://qiita-image-store.s3.amazonaws.com/0/159417/b21e989f-b9b5-dbbc-fa46-fef4804db67e.png)

おっ、いけてる。

どれぐらいの構造まで　読み込んでくれるんだろうか？

「JsonUtility をつかって Unity で JSON を取り扱う方法」
http://qiita.com/sea_mountain/items/6513b330983ffa003959

Dictionary には対応していないと書いてあるな。

```JSON
※前略
 "tileproperties":
    {
     "32":
        {
         "Collision":true
        },
     "33":
        {
         "Collision":true
        },
     "34":
        {
         "Collision":true
        },
     "35":
        {
         "Collision":true
        }
    },
※後略
```

こんなデータはどうやって読込めばいいんだ？

「【Unity】JsonUtility で List<T> と Dictionary<TKey,TValue> シリアライズする」浮遊島
http://kou-yeung.hatenablog.com/entry/2015/12/31/014611

サンプル・コードを利用してみる。

![20170606f1.png](https://qiita-image-store.s3.amazonaws.com/0/159417/cf3e3220-9c7e-2631-850a-eb82540a3dd2.png)

連想配列にしようとしたところ以外は　読込めている。
キーは数字ではなく　文字列にしたらいいのか？
そうでもないみたいだ。

ネストしていない JSON なら Dictionary にもできたが、ネストしている場合はどうすればいいのか。

「【Unity】Unity 5.3 新機能「ISerializationCallbackReceiver」」コガネブログ
http://baba-s.hatenablog.com/entry/2016/01/19/100000

コールバック関数で何かできないだろうか？

「ISerializationCallbackReceiver」
https://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.html

# ネストしていたら使えない場合

じゃあ

tileproperties.json

```JSON
    {
     "32":
        {
         "Collision":true
        },
     "33":
        {
         "Collision":true
        },
     "34":
        {
         "Collision":true
        },
     "35":
        {
         "Collision":true
        }
    }
```

のように外出しして　運用するか？
読取はスルーされた。

連想配列の出力形式が違うのではないか。

```JSON
{
    "keys":[1000,2000],
    "values":[
        {"name":"スライム","skills":["攻撃"]},
        {"name":"キングスライム","skills":["攻撃","回復"]}
    ]
}
```

JsonUtilityは キーと、バリューが別々に出る形式か。

# MiniJSON も調べてみる

「darktable/MiniJSON.cs」
https://gist.github.com/darktable/1411710

.cs ファイル１つか。助かる。
MIT License だろうか。

```C#
    [MenuItem("Tool/Json Test Tileproperties MiniJSON")]
    static void JsonTestTilepropertiesMiniJSON()
    {
        string jsonString =
@"
    {
     ""32"":
        {
            ""collision"":true
        },
     ""33"":
        {
            ""collision"":true
        },
     ""34"":
        {
            ""collision"":true
        },
     ""35"":
        {
            ""collision"":true
        }
    }
"
;

        Dictionary<string, object> dict = Json.Deserialize(jsonString) as Dictionary<string, object>;

        Debug.Log("deserialized: " + dict.GetType());
        Debug.Log("dict['32']['collision']: " + ((Dictionary<string, object>)dict["32"])["collision"]);
        Debug.Log("dict['33']['collision']: " + ((Dictionary<string, object>)dict["33"])["collision"]);
        Debug.Log("dict['34']['collision']: " + ((Dictionary<string, object>)dict["34"])["collision"]);
        Debug.Log("dict['35']['collision']: " + ((Dictionary<string, object>)dict["35"])["collision"]);

        var str = Json.Serialize(dict);

        Debug.Log("serialized: " + str);
    }
```

こういう書き方ならいけるようだ。併用するか。

関連記事：http://qiita.com/muzudho1/items/1cb926c980b8327f093d

# MiniJSON でのパーサーの書き方

MiniJSON は C#ファイル１つなので、Git Hub の[Raw]ボタンを押して [Ctrl]+[A] で全選択するなどして、MiniJSON.cs を作っておく。

Unityで連想配列を読む形のコードを掲載しておく。

Assets/Editor/MiniJsonTest

```C#
using MiniJSON;
using System.Collections.Generic;
using UnityEngine;
using NUnit.Framework;
using System.Text;

/// <summary>
/// MiniJSON の使用例
/// </summary>
class MiniJsonTest
{

    /// <summary>
    /// Unityエディターのメインメニュー [Window] - [Test Runner] - [Run All] で単体テスト。
    /// </summary>
    [Test]
    public static void TestMiniJson_SampleJson1()
    {
        object obj = Json.Deserialize(
            @"{""number"":32}"
            );

        Debug.Assert(null != obj);
    }

    [Test]
    public static void TestMiniJson_SampleJson2()
    {
        Dictionary<string, object> dic1 = Json.Deserialize(
            @"{""number"":32}"
            ) as Dictionary<string, object>;

        Debug.Assert(null != dic1);
    }

    const string sampleJson =
@"
{
    ""number"":32,
    ""text"":""hello world"",
    ""properties"":
    {
        ""32"":
        {
            ""visible"":true
        },
        ""33"":
        {
            ""visible"":true
        },
        ""34"":
        {
            ""visible"":true
        },
        ""35"":
        {
            ""visible"":true
        }
    }
}
";

    [Test]
    public static void TestMiniJson_SampleJson3()
    {
        Dictionary<string, object> dic1 = Json.Deserialize(sampleJson) as Dictionary<string, object>;

        Debug.Assert(null != dic1);

        Debug.Assert(32 == (long)dic1["number"]);
        Debug.Assert("hello world" == (string)dic1["text"]);
        Debug.Assert(true == (bool)(
            (Dictionary<string, object>)
            ((Dictionary<string, object>)
            dic1["properties"]
            )["32"]
            )["visible"]
            );
        Debug.Assert(true == (bool)(
            (Dictionary<string, object>)
            ((Dictionary<string, object>)
            dic1["properties"]
            )["33"]
            )["visible"]
            );
        Debug.Assert(true == (bool)(
            (Dictionary<string, object>)
            ((Dictionary<string, object>)
            dic1["properties"]
            )["34"]
            )["visible"]
            );
        Debug.Assert(true == (bool)(
            (Dictionary<string, object>)
            ((Dictionary<string, object>)
            dic1["properties"]
            )["35"]
            )["visible"]
            );
    }

    [Test]
    public static void TestMiniJson_CreateFromJSON()
    {
        MiniJsonObject obj = CreateFromJSON(sampleJson);

        Debug.Assert(32 == obj.number);
        Debug.Assert("hello world" == obj.text);
        Debug.Assert(true == (bool)((Dictionary<string, object>)obj.properties["32"])["visible"]);
        Debug.Assert(true == (bool)((Dictionary<string, object>)obj.properties["33"])["visible"]);
        Debug.Assert(true == (bool)((Dictionary<string, object>)obj.properties["34"])["visible"]);
        Debug.Assert(true == (bool)((Dictionary<string, object>)obj.properties["35"])["visible"]);
    }

    [Test]
    public static void TestMiniJson_ToJSON()
    {
        MiniJsonObject obj = CreateFromJSON(sampleJson);

        // 空白を飛ばしたものと比較したいが、文字列の中の空白は飛ばしてはいけないので、むずかしい。
        // obj.ToJson()={"number":32,"text":"hello world","properties":{"32":{"visible":true},"33":{"visible":true},"34":{"visible":true},"35":{"visible":true}}}
        // expect      ={"number":32,"text":"helloworld","properties":{"32":{"visible":true},"33":{"visible":true},"34":{"visible":true},"35":{"visible":true}}}

        string s = obj.ToJson();
        //string expect = sampleJson.Replace("\r", "").Replace("\n", "").Replace(" ", "");
        string expect = @"{""number"":32,""text"":""hello world"",""properties"":{""32"":{""visible"":true},""33"":{""visible"":true},""34"":{""visible"":true},""35"":{""visible"":true}}}";

        Debug.Assert(expect == s, "obj.ToJson()=" + s + " expect="+ expect);
    }

    public static MiniJsonObject CreateFromJSON(string jsonString)
    {
        MiniJsonObject obj = new MiniJsonObject();

        Dictionary<string, object> firstDic = Json.Deserialize(jsonString) as Dictionary<string, object>;

        foreach (KeyValuePair<string, object> firstEntry in firstDic)
        {
            switch (firstEntry.Key)
            {
                case "number": obj.number = (long)firstEntry.Value; break;
                case "text": obj.text = (string)firstEntry.Value; break;
                case "properties":
                    {
                        Dictionary<string, object> propertiesDic = (Dictionary<string, object>)firstEntry.Value;
                        foreach (KeyValuePair<string, object> propertiesEntry in propertiesDic)
                        {
                            Dictionary<string, object> customDic = (Dictionary<string, object>)propertiesEntry.Value;
                            foreach (KeyValuePair<string, object> customEntry in customDic)
                            {
                                // チェックせずに丸ごと追加してもいいのでは。
                                switch (customEntry.Key)
                                {
                                    case "visible":
                                        obj.properties.Add(propertiesEntry.Key, customDic);
                                        break;
                                }
                            }
                        }
                    }
                    break;
            }
        }

        return obj;
    }

    public class MiniJsonObject
    {
        /// <summary>
        /// MiniJSONでは、数字は long型として扱う
        /// </summary>
        public long number;
        public string text;

        /// <summary>
        /// MiniJSONでは、シンプルな構造に限り、連想配列も読取可能
        /// </summary>
        [SerializeField]
        public Dictionary<string, object> properties;

        /// <summary>
        /// Unit Test 用のデフォルト・コンストラクタ
        /// </summary>
        public MiniJsonObject()
        {
            properties = new Dictionary<string, object>();
        }

        public string ToJson()
        {
            StringBuilder sb = new StringBuilder();

            sb.Append("{");
            sb.Append(@"""number"":");sb.Append(Json.Serialize(number));
            sb.Append(",");
            sb.Append(@"""text"":"); sb.Append(Json.Serialize(text));
            sb.Append(",");
            sb.Append(@"""properties"":"); sb.Append(Json.Serialize(properties));
            sb.Append("}");

            return sb.ToString();
        }
    }
}
```

# MiniJSON用のオブジェクトの作り方

MiniJSON で読込むと、ネストした連想配列の形で返ってくる。

```C#
Dictionary<string, object> firstDic = Json.Deserialize(jsonString) as Dictionary<string, object>;
```

で、連想配列では使いにくいから　オブジェクトの形にしよう、というものが前に書いたコードの MiniJsonObject だ。
これをもっと　楽に書きたい。

クラス１つにまとめた方が　形がいいので、全貌はこうだろうか。
ファイルの冒頭には次のように書く。

```C#
using MiniJSON;
using System.Collections.Generic;
```

```C#
public class MiniJsonObject
{
    public static MiniJsonObject CreateFromJSON(string jsonString)
    {
        // ここにJSON読み取り処理を書く
    }

    // ここにデータ構造を作る

    public string ToJson()
    {
        // ここにJSON出力処理を書く
    }
}
```

ここで、

- 数字は long 型
- 文字列は string 型

という２つの型に絞って解説する。連想配列は、

- Dictionary<string,★>

の形でだけ作れるものとし、★ には

- long
- string
- Dictionary<string,★>

だけ入れる、というふうに絞ることにする。これで大分　説明が楽になる。

JsonMINI は、

```C#
Dictionary<string, object> dic
```

のように、連想配列で結果を返してくる。
JSONファイルは、テキストファイルとして読込んでおいて、

```C#
Dictionary<string, object> dic1 = Json.Deserialize(sampleJson) as Dictionary<string, object>;
```

といった形で　テキストを連想配列に変換する。

この連想配列の中身を読み取るには、foreach 文を使うと分かりやすい。

```C#
foreach (KeyValuePair<string, object> entry in dic)
{
}
```

この形にすれば、C#の一般的な書き方だ。entry.Key には 項目名が入っていると考える。

```C#
switch (entry.Key)
{
    case "number": obj.number = (long)entry.Value; break;
    case "text": obj.text = (string)entry.Value; break;
    case "properties":
        // 略
        break;
}
```

こう書けば、数字と文字列の取得は簡単だろう。
連想配列は、今説明していることをネストすればいい。

## ネスト部分の書き方

ループをネストすると　変数名を見分けるのが大変になるので、
関数に切り分けるのも手。

例えば JSONの配列であれば

```C#
foreach (KeyValuePair<string, object> entry in dic)
{
    switch (entry.Key)
    {
        case "layers": ParseArray1((List<object>)entry.Value, obj); break;
    }
}
```

といった風に別関数に飛ばしてしまう。

```C#
    static void ParseLayers(List<object> array, MiniJSONObject obj)
    {
        foreach (Dictionary<string,object> dic in array)
        {
            switch ((string)dic["name"])
            {
                case "Apple":
                    Debug.Log("りんご");
                    break;
                case "Banana":
                    Debug.Log("ばなな");
                    break;
            }
        }
    }
```

配列の要素が　オブジェクトの場合、Dictionary<string,object> で取ればいい。

# 型が分からない場合

UnityEngine名前空間のDebug.Log を利用して、型名をログに出していく。

```C#
Debug.Log("groundの型=" + ground.GetType().Name);
```

# int型の配列と思っていても、List<object>にlong型の要素が入っている

MiniJSONを使うのだったら、long型の配列にした方がいいのか？

```C#
foreach (object element in (List<object>)dic["data"])
{
    obj.arrayData.Add((int)((long)element));
}
```