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] と進んで実行してみる。
おっ、いけてる。
どれぐらいの構造まで 読み込んでくれるんだろうか?
「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
サンプル・コードを利用してみる。
連想配列にしようとしたところ以外は 読込めている。
キーは数字ではなく 文字列にしたらいいのか?
そうでもないみたいだ。
ネストしていない 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));
}