Unityの標準機能となったJSON化
データをJSON化するのにずっとLitJsonを使っていました。UnityのアップデートでUnityの標準機能としてJSONを扱えるようになりましたが、LitJsonと比べるといろいろと使いにくいと感じたところも多かったのでずっとLitJsonを使い続けていました。
でも、なるべくUnityの標準機能を使いたいと思い、いろいろと調べてLitJsonのように使えることがわかったのでそのことをメモしておきたいと思います。
蛇足ですが、"JsonUnity"ではなく"JsonUtility"なのでお間違えなく。
LitJsonからJsonUtilityに移行したい理由(LitJsonのデメリット)
なるべくUnityの標準機能を使いたい理由は以下のようなLitJsonのデメリットがあるからです。
- Unityがアップデートしたときに急に使えないとかバグができるとなるのが怖いから。
- データ化(JSONから戻したとき)したときに内容不備によるエラーがあってもLitJsonだと原因を追いづらい。
- コンストラクタがあるクラスをデータ化するとエラーが出る(これ致命的)。
あと、コンソールに常にLitJsonのアラートが出ているのが気持ち悪いからです(笑)
JsonUtilityでできないと思って困っていたこと
JSON化したいクラスのメンバ変数はPublicにしておかないといけません。これはLitJsonでも同じですPublicにするか、[SerializeField]をつける必要があります。ただ、JsonUtilityではLitJsonと違ってPublicにしただけでは入れ子にするクラスがJSON化されないという問題がありました。
例えば、主人公クラスの中に持ち物クラスなどを入れて主人公クラスをJSON化すると、持ち物クラスのデータは無視されてJSON化されます。持ち物クラスをList型に入れてもそのListが無視されてJSON化されます。
// 主人公クラスをインスタンス化 Person hero = new Person(); hero.name = "napa"; hero.lv = 1; // 持ち物クラスをインスタンス化(クラスの入れ子) hero.item = new Item(); hero.item.name = "red";
これをJSON化すると、
{"name":"napa","lv":1}
となり、itemクラスのデータは完全に無視されます。
JsonUtilityで入れ子のクラスもJSON化する
Unityのリファレンスに書いていあるっぽいんですが、パッと見たところわかりにくいです。
https://docs.unity3d.com/jp/540/ScriptReference/JsonUtility.FromJson.html
要するに入れ子にしたいクラスにSerializable属性([System.Serializable])を付けましょうってことです。
// 持ち物クラス [System.Serializable] class Item { public string name; }
これでこのクラスを入れ子にしても
{"name":"napa","lv":1,"item":{"name":"red"}}
このようにJSON化されます。
ちなみに継承先クラスに[System.Serializable]が付いていれば、継承元クラスに[System.Serializable]を付けていなくても継承元クラスの変数はJSON化されるようです。
JsonUtilityでPrivateな変数をJSON化する
JsonUtilityでJSON化したい変数はPublicにする必要がありますが、[SerializeField]を付ければPrivateでもJSON化することができます(入れ子のクラスも同様です)。
入れ子のクラスの場合、変数に[SerializeField]を付けていてもクラスに[System.Serializable]が付いていないとJSON化されません(当然ですが)。
ルートがList型のデータをJSON化する場合は他のクラスでラップする
なんでかわかりませんが、ルートがList型だとJSON化できません。そのためラップするためのクラスを用意して、そこにListを入れてJSON化するようにしましょう。もちろんデータ化するときもクラスでラップすることを前提に用意しておく必要があります。
List<Hero> heroes = new List<Hero>(); // List<Hero>型のheroesにデータを追加する string json = JsonUtility.ToJson(heroes);
このようにルートがList型のデータをJSON化しても無視されます。もしかすると他のコレクションもダメかもしれません(試してませんが)。
class Wrapper { public List<Hero> heroes; }
こういうクラスを用意しておいて、
Wrapper wrapper = new Wrapper(); wrapper.heroes = new List<Hero>(); // wrapperにあるList<Hero>型のheroesにデータを追加する string json = JsonUtility.ToJson(wrapper);
こうすればJSON化できます。
JsonUtilityの注意点とまとめ
- List型や配列には対応しているがDictionary型には対応していない(無視される)。
- List型に対応しているがルートがList型だと無視される。
- インターフェースを実装したり抽象クラスを継承したクラスでも使える。
- コンストラクタが実装されているクラスでも使える(LitJsonではデータに戻すときにエラーになっていた)。検証済み
- インターフェースや抽象クラスの型でポリモーフィズム化したクラスは無視される(Json化されない)。
- JsonUtility.ToJson<T>()でデータに戻すとき、T型をインターフェースや抽象クラスの型にしてアップキャストできない(エラー)。
- Float型なども使える。ただし、精度が不安なのでおすすめできない。
追記:ISerializationCallbackReceiverを実装する
JSON化したいクラスにISerializationCallbackReceiverを実装すると、シリアライズ前後にコールバックされるメソッドを実装できます。
JSON化前後に処理を入れたい時に便利です。
public void OnAfterDeserialize() { // JSON化する直前に呼ばれるメソッド } public void OnBeforeSerialize() { // JSON化した直後に呼ばれるメソッド }
追記:enum型のシリアル化
JSON化したいクラスにenum型の定数が入っている場合、enum型の定義に[System.Serializable]の属性をつけなくてもシリアル化されます。enum型はコンパイル時定数だそうです。なので内部的には(デフォルトなら)int型に置き換えられているので、enum型を使っているクラスに属性が付いて、enum型を宣言するときにpublicにするか[SerializeField]を付けていれば大丈夫そうです。
追記:ScriptableObjectを継承したクラスのシリアル化
ScritableObjectを継承したクラスはシリアル化されないようです。GameObjectなどをシリアル化するのと同じようなことになります。
ただ、エラーにならず「"instanceID":11718」という文字でシリアル化されます。