データをJSON化するのにずっとLitJsonを使っていました。UnityのアップデートでUnityの標準機能としてJSONを扱えるようになりましたが、LitJsonと比べるといろいろと使いにくいと感じたところも多かったのでずっとLitJsonを使い続けていました。

でも、なるべくUnityの標準機能を使いたいと思い、いろいろと調べてLitJsonのように使えることがわかったのでそのことをメモしておきたいと思います。

LitJsonからJsonUtilityに移行したい理由(LitJsonのデメリット)

なるべくUnityの標準機能を使いたい理由は以下のようなLitJsonのデメリットがあるからです。

  • Unityがアップデートしたときに急に使えないとかバグができるとなるのが怖いから。
  • データ化(JSONから戻したとき)したときに内容不備によるエラーがあってもLitJsonだと原因を追いづらい。
  • コンストラクタがあるクラスをデータ化するとエラーが出る(これ致命的)。

あと、コンソールに常にLitJsonのアラートが出ているのが気持ち悪いからです(笑)

JsonUtilityでできないと思って困っていたこと

JSON化したいクラスのメンバ変数はPublicにしておかないといけません。これはLitJsonでも同じです。ただ、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型だと無視される。
  • JsonUtility.ToJson<T>()でデータに戻すとき、インターフェースや抽象クラスの型にアップキャストできない
  • LitJsonではデータに戻すときにエラーになっていたコンストラクタが実装されているクラスでも使える
  • 抽象クラスを継承した引数付きコンストラクタが実装されているクラスでも使える。
  • Float型なども使える。ただし、精度が不安なのでおすすめできない。

追記:ISerializationCallbackReceiverを実装する

JSON化したいクラスにISerializationCallbackReceiverを継承すると、シリアライズ前後にコールバックされるメソッドを実装できます。
JSON化前後に処理を入れたい時に便利です。

public void OnAfterDeserialize()
{
// JSON化する直前に呼ばれるメソッド
}
public void OnBeforeSerialize()
{
// JSON化した直後に呼ばれるメソッド
}