JsonUtility をつかって Unity で JSON を取り扱う方法

  • 55
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

Unity 5.3 から Unity で JSON を扱うことのできる API が追加されました。
Unity 5.3 リリースノート

今まで JSON を扱うためには、アセットやライブラリを追加する必要がありましたが、これからは JsonUtility を使って JSON 扱うことができるようになります。

そこで今まで利用していたライブラリ (MiniJSON) を意識しつつ、JsonUtility の使い方をまとめます。

本記事では Unity 5.3.4f1 を使用しています。

MiniJSON とは

MiniJSON とは以下で公開されている MIT ライセンスの JSON パーサです。

https://gist.github.com/darktable/1411710

MiniJSON の使い方

MiniJSON では以下のように JSON を Dictionary にしたり、Dictionary を JSON にします。
そのため、JSON の構造が確定していなくても、受け取った JSON のすべてのパラメータを Dictionary の値として取得できます。

想定するJSON
{  
   "id":100,
   "name":"テストアイテム",
   "description":"説明だよ"
}
string itemJson = "{ \"id\": 100, \"name\": \"テストアイテム\", \"description\": \"説明だよ\" }";
// JSON(string) -> Dictionary
Dictionary<string, object> itemDic = Json.Deserialize(itemJson) as Dictionary<string, object>;

Debug.Log("item id " + int.Parse(itemDic["id"].ToString()));
Debug.Log("item name " + (string)itemDic["name"]);
Debug.Log("item description " + (string)itemDic["description"]);

Screen Shot 2016-04-24 at 12.28.08 PM.png

// Dictionary -> JSON(string)
string json = Json.Serialize(itemDic);
Debug.Log("Json str " + json);

JsonUtility とは

Unity 5.3 から追加された Unity で JSON を扱うための API です。

Unity 公式ドキュメント JsonUtility API
Unity 公式ドキュメント JSON 形式にシリアライズ

JsonUnitity の使い方

JsonUtility では、まず JSON の構造を記述する必要があります。その構造を元にシリアライズするのが JsonUtility です。
そのため、構造に規定されていないパラメータや、Dictionary 等の対応していない型に関しては無視されます。 (制約に関しては後の節で述べます)

以下のように JSON 構造の規定し、各 API を使ってみます。

[Serializable]
public class Item
{
    public int id;
    public string name;
    public string description;
}

JSON からインスタンスを作成 ( FromJson() )

string itemJson = "{ \"id\": 100, \"name\": \"テストアイテム\", \"description\": \"説明だよ\" }";
Item item = JsonUtility.FromJson<Item>(itemJson);

Debug.Log("item id " + item.id);
Debug.Log("item name " + item.name);
Debug.Log("item description " + item.description);

Screen Shot 2016-04-24 at 12.51.27 PM.png

規定したクラスのプロパティ名と同じキー名の値が代入されていることがわかります。

Unity API リファレンス FromJson

JSON へのシリアライズ ( ToJson() )

先に作成した item インスタンスのパラメータを JSON として取得します。

string serialisedItemJson = JsonUtility.ToJson(item);
Debug.Log("serialisedItemJson " + serialisedItemJson);

Screen Shot 2016-04-24 at 12.55.34 PM.png

Unity API リファレンス ToJson

JSON の変更をインスタンスに反映する

// 特定のデータのみ更新する
string updatedData = "{ \"name\": \"更新されたアイテム\" }";
JsonUtility.FromJsonOverwrite(updatedData, item);

Debug.Log("item id " + item.id);
Debug.Log("item name " + item.name);
Debug.Log("item description " + item.description);

// 試しに再度 JSON にしてみる
string updatedJson = JsonUtility.ToJson(item);
Debug.Log("updatedJson " + updatedJson);

Screen Shot 2016-04-24 at 7.40.52 PM.png

name の値だけが更新されました。
ドキュメントによると、再度アロケーションが走らずに、値の更新が行われます。また、Serializer が対応していないフィールド (private フィールド、static フィールド、 NonSerialized アトリビュートがついてるフィールド) の場合は無視される様です。

Unity API リファレンス FromJsonOverwrite

他の構造を試してみる

private フィールドの場合

private のままでは FromJsonToJson 時に無視されます。

[Serializable]
public class Item2
{
    public int id;
    public string name;
    private string description = "デフォルト値";
}
string itemJson = "{ \"id\": 100, \"name\": \"テストアイテム\", \"description\": \"説明だよ\" }";
// JSON -> Item2 インスタンス
Item2 item2 = JsonUtility.FromJson<Item2>(itemJson);

Debug.Log("itemJson " + itemJson);
Debug.Log("item2 id " + item2.id);
Debug.Log("item2 name " + item2.name);

// private な値なためリフレクションで取得する
FieldInfo field = item2.GetType().GetField("description", (BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance));
string description = field.GetValue(item2) as string;

Debug.Log("item2 description " + description);

// Item2 インスタンス -> JSON
string serialisedItemJson = JsonUtility.ToJson(item2);
Debug.Log("serialisedItemJson " + serialisedItemJson);

Screen Shot 2016-04-24 at 2.26.35 PM.png

そこで、 SerializeField 属性を追加するとシリアライズ対象にすることが出来ます。

参考サイト: 【Unity5.3】JsonUtility使い方まとめ

[Serializable]
public class Item2
{
    public int id;
    public string name;
    [SerializeField]
    private string description = "デフォルト値";
}

以下は、 [SerializeField] を設定して、同じ JSON string とシリアライズ・デシリアライズ処理をした場合の結果です。
JSON string で設定した description が代入されていることが分かります。

Screen Shot 2016-04-24 at 2.52.45 PM.png

JSONにクラスに定義されていないキー名のデータが存在する場合

クラスに定義されていない値が JSON に存在する場合は無視されます。

[Serializable]
public class Item3
{
    public int id;
    public string name;
    public string description = "デフォルト値";
}

JSON に test というパラメータを追加してみます。

string itemJson = "{ \"id\": 100, \"name\": \"テストアイテム\", \"description\": \"説明だよ\", \"test\": \"テストだよ\" }";
Item3 item3 = JsonUtility.FromJson<Item3>(itemJson);

Debug.Log("itemJson " + itemJson);
Debug.Log("item3 id " + item3.id);
Debug.Log("item3 name " + item3.name);
Debug.Log("item4 description " + item3.description);

Debug.Log("--- フィールド名一覧 ---");
FieldInfo[] fields = item3.GetType().GetFields();
for (int i = 0; i < fields.Length; i++) {
    Debug.Log(fields[i].Name);
}
Debug.Log("----------------------");

string serialisedItemJson = JsonUtility.ToJson(item3);
Debug.Log("serialisedItemJson " + serialisedItemJson);

インスタンスにそのパラメータは存在していません。もちろん、そのインスタンスを JSON にシリアライズした時も存在しません。

Screen Shot 2016-04-24 at 3.13.40 PM.png

配列がある場合

そのまま問題なくシリアライズ・デシリアライズできました。

[Serializable]
public class Item4
{
    public int id;
    public string name;
    public string description = "デフォルト値";
    public int[] children;
}
string itemJson = "{ \"id\": 100, \"name\": \"テストアイテム\", \"description\": \"説明だよ\", \"children\": [1, 2, 3] }";
Item4 item4 = JsonUtility.FromJson<Item4>(itemJson);

Debug.Log("itemJson " + itemJson);
Debug.Log("item4 id " + item4.id);
Debug.Log("item4 name " + item4.name);
Debug.Log("item4 description " + item4.description);

string serialisedItemJson = JsonUtility.ToJson(item4);
Debug.Log("serialisedItemJson " + serialisedItemJson);

Screen Shot 2016-04-24 at 3.50.55 PM.png

ネストした要素の場合

以下の様な JSON の場合、ネストしている要素の Serializable なクラスを作成するとシリアライズ・デシリアライズできました。

想定するJSON
{  
   "id":100,
   "name":"テストアイテム",
   "description":"説明だよ",
   "color":{  
      "id":1,
      "name":"black"
   }
}
[Serializable]
public class Item5
{
    public int id;
    public string name;
    public string description = "デフォルト値";
    public ItemColor color;
}

[Serializable]
public class ItemColor
{
    public string id;
    public string name;
}
string itemJson = "{ \"id\": 100, \"name\": \"テストアイテム\", \"description\": \"説明だよ\", \"color\": { \"id\" : 1, \"name\": \"black\" } }";
Item5 item5 = JsonUtility.FromJson<Item5>(itemJson);

Debug.Log("itemJson " + itemJson);
Debug.Log("item5 id " + item5.id);
Debug.Log("item5 name " + item5.name);
Debug.Log("item5 description " + item5.description);

string serialisedItemJson = JsonUtility.ToJson(item5);
Debug.Log("serialisedItemJson " + serialisedItemJson);

Screen Shot 2016-04-24 at 4.05.04 PM.png

JsonUtility の制約

公式ドキュメント によると JsonUtility には以下の制約があります。

  • Dictionary はサポートされない
  • プリミティブ型を直接指定できない
    • クラスや構造体でラップすれば使える

使用できる型は Unity の Inspector と同様だと理解すればよさそうです。

JsonUtility でどうしても Dictionary を使いたい時

シリアライズのタイミングで以下のコールバックを受け取ることができるため、これを利用してデータを詰める処理を書く事ができるようです。

JsonUtility のメリット

パフォーマンス面で他の JSON パーサよりも優れている

JsonUtilityは(機能は .NET JSON より少ないですが)、よく使用されている .NET JSON よりも著しく早いことが、ベンチマークテストで示されています。
GCメモリの使用が、以下のように最小に抑えられています。
- ToJson()は、返されたストリングにのみ GC メモリをアロケーションします。
- FromJson()は、返されたオブジェクトにのみ GC メモリをアロケーションします。必要な場合は、サブオブジェクトも同様です (例えば、配列を含むオブジェクトをデシリアライズする場合、GC メモリは配列にアロケーションされます)。
- FromJsonOverwrite()は、書き込まれたフィールド (例えば、ストリングと配列) に必要な場合にのみ GC メモリをアロケーションします。JSON に上書きされたすべてのフィールドが値型の場合は、GC メモリはアロケーションされません。

http://docs.unity3d.com/jp/current/Manual/JSONSerialization.html

通常、JSON パーサを使うと対象の JSON がすべてメモリに展開されてしまいますが、JsonUtility ではその点を考慮されているようですね。 ( Serializable を指定しなければいけない分、この恩恵を受けられるのだろうと思っています)

Unite 2016 のセッション でも、通常の JSON パーサはリフレクションを多用しているため、重くなってしまうので、 JsonUtility を使ってみて欲しいという話がありました。

バックグラウンドスレッドでの呼び出しが可能

JsonUtility API はバックグラウンドスレッドで処理することが可能です。他のマルチスレッドコードの場合と同様に、一方でシリアライズ/デシリアライズを行っている間に、他のスレッド上のオブジェクトにアクセスしたり変えたりしないように注意してください。

http://docs.unity3d.com/jp/current/Manual/JSONSerialization.html

Unity のほとんどの API はメインスレッドから呼び出す必要がありますが、 JsonUtility はバックグラウンドで呼び出せる様です。
これなら、JSON データを扱う処理をすべてバックグラウンドに移せますね。

まとめ

今まで使っていた JSON パーサ (MiniJSON) ではどんなデータでも Dictionary にできる反面、メモリ使用量などの問題がありました。
JsonUtility を使うことでこの問題に対応出来ますが、事前に JSON 形式を定義しておく必要があります。
MiniJSON と JsonUtility でそれぞれ JSON の扱い方や、API インターフェースもかなり異なるため、上記を参考に、色んな形式の JSON を扱ってみてください。

文中に記載した以外の参考サイト