LoginSignup
26
17

More than 5 years have passed since last update.

UnityでJSONを読み書きしようぜ(特にDictionaryで)

Last updated at Posted at 2017-06-05

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

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

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

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

おっ、いけてる。

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

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

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

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

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

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

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

20170606f1.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

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

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

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

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

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

MiniJSON も調べてみる

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

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

    [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#ファイル1つなので、Git Hub の[Raw]ボタンを押して [Ctrl]+[A] で全選択するなどして、MiniJSON.cs を作っておく。

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

Assets/Editor/MiniJsonTest

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 で読込むと、ネストした連想配列の形で返ってくる。

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

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

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

using MiniJSON;
using System.Collections.Generic;
public class MiniJsonObject
{
    public static MiniJsonObject CreateFromJSON(string jsonString)
    {
        // ここにJSON読み取り処理を書く
    }

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

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

ここで、

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

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

  • Dictionary

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

  • long
  • string
  • Dictionary

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

JsonMINI は、

Dictionary<string, object> dic

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

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

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

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

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

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

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

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

ネスト部分の書き方

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

例えば JSONの配列であれば

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

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

    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 で取ればいい。

型が分からない場合

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

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

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

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

foreach (object element in (List<object>)dic["data"])
{
    obj.arrayData.Add((int)((long)element));
}
26
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
17