Edited at

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

More than 1 year has passed since last update.

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));
}