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

Unity 5.3 から Unity で JSON を扱うことのできる API が追加されました。
[Unity 5.3 リリースノート](https://unity3d.com/jp/unity/whats-new/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:想定するJSON
{  
   "id":100,
   "name":"テストアイテム",
   "description":"説明だよ"
}
```

```csharp
string itemJson = "{ \"id\": 100, \"name\": \"テストアイテム\", \"description\": \"説明だよ\" }";
```

```csharp
// 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](https://qiita-image-store.s3.amazonaws.com/0/1635/35667dbe-1ada-200b-9f88-d09c2f979d53.png)


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


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

[Unity 公式ドキュメント JsonUtility API](http://docs.unity3d.com/ScriptReference/JsonUtility.html)
[Unity 公式ドキュメント JSON 形式にシリアライズ](http://docs.unity3d.com/jp/current/Manual/JSONSerialization.html)

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


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

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

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

```csharp
string itemJson = "{ \"id\": 100, \"name\": \"テストアイテム\", \"description\": \"説明だよ\" }";
```

```csharp
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](https://qiita-image-store.s3.amazonaws.com/0/1635/4e76a173-fe37-dce3-84eb-7a0cc7321731.png)

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

[Unity API リファレンス FromJson](http://docs.unity3d.com/ScriptReference/JsonUtility.FromJson.html)

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

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

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

![Screen Shot 2016-04-24 at 12.55.34 PM.png](https://qiita-image-store.s3.amazonaws.com/0/1635/cdde9722-2915-9ce2-0d5f-d6d4bdb426d6.png)

[Unity API リファレンス ToJson](http://docs.unity3d.com/ScriptReference/JsonUtility.ToJson.html)

### 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](https://qiita-image-store.s3.amazonaws.com/0/1635/f691d723-51ac-29f7-8433-990d0908423a.png)


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

[Unity API リファレンス FromJsonOverwrite](http://docs.unity3d.com/ScriptReference/JsonUtility.FromJsonOverwrite.html)


## 他の構造を試してみる
### private フィールドの場合

private のままでは `FromJson` や `ToJson` 時に無視されます。

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

```csharp
string itemJson = "{ \"id\": 100, \"name\": \"テストアイテム\", \"description\": \"説明だよ\" }";
```

```csharp
// 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](https://qiita-image-store.s3.amazonaws.com/0/1635/6fa379f3-51e9-f3b5-0d41-859b79a5d29f.png)

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

参考サイト: [【Unity5.3】JsonUtility使い方まとめ](http://hatena.phalusamil.com/entry/2015/12/10/201509)

```csharp
[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](https://qiita-image-store.s3.amazonaws.com/0/1635/73a3c54b-535e-4e4a-9aac-cc2e66aaa213.png)

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

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

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

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

```csharp
string itemJson = "{ \"id\": 100, \"name\": \"テストアイテム\", \"description\": \"説明だよ\", \"test\": \"テストだよ\" }";
```

```csharp
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](https://qiita-image-store.s3.amazonaws.com/0/1635/1dab8e29-2e2b-5e27-4078-b5a8a2ed6add.png)

### 配列がある場合
そのまま問題なくシリアライズ・デシリアライズできました。

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

```csharp
string itemJson = "{ \"id\": 100, \"name\": \"テストアイテム\", \"description\": \"説明だよ\", \"children\": [1, 2, 3] }";
```

```csharp
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](https://qiita-image-store.s3.amazonaws.com/0/1635/54a1d8ad-fa8f-3e9e-305a-888e92ddeea2.png)


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

```json:想定するJSON
{  
   "id":100,
   "name":"テストアイテム",
   "description":"説明だよ",
   "color":{  
      "id":1,
      "name":"black"
   }
}
```


```csharp
[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;
}
```

```csharp
string itemJson = "{ \"id\": 100, \"name\": \"テストアイテム\", \"description\": \"説明だよ\", \"color\": { \"id\" : 1, \"name\": \"black\" } }";
```

```csharp
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](https://qiita-image-store.s3.amazonaws.com/0/1635/62015725-8241-f86a-739f-e00e0be4ca8d.png)

# JsonUtility の制約
[公式ドキュメント](http://docs.unity3d.com/jp/current/Manual/JSONSerialization.html) によると JsonUtility には以下の制約があります。

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

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

## JsonUtility でどうしても Dictionary を使いたい時
- 参考
  - [Unity5.3でJsonを扱うまとめ](http://qiita.com/toRisouP/items/53be639f267da8845a42)
  - [【Unity】Unity 5.3 新機能「ISerializationCallbackReceiver」](http://baba-s.hatenablog.com/entry/2016/01/19/100000)
  - [Unity API ドキュメント ISerializationCallbackReceiver.OnBeforeSerialize](http://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.OnBeforeSerialize.html)

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

# 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 のセッション](http://japan.unity3d.com/unite/unite2016/files/DAY1_1330_Room1_HarknessDundore_Long_Big.pdf) でも、通常の 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 を扱ってみてください。

## 文中に記載した以外の参考サイト
- [Unity 5.3で実装されたJsonUtilityの注意点まとめ](http://ch.nicovideo.jp/ntmi/blomaga/ar927398)
- [[Unity] JsonUtilityについて](http://westhillapps.blog.jp/archives/47155877.html)
- [[Unity] MiniJSON 使って json 読み込み](http://qiita.com/phi/items/914bc839b543988fc0ec)