Unity5.3でJsonを扱うまとめ

  • 156
    いいね
  • 3
    コメント

はじめに

Unity5.3がリリースされ、やっとUnity標準でJsonが扱えるようになりました。

というわけでとりあえず触ってみて使い方をメモしました。
見落としている機能や使い方が違う場合もあるので過信しないでください。

前提

Jsonから相互変換したいクラスにはSerializableAttributeをつけておく

Object → Json

ObjectからJsonに変換する

JsonUtility.ToJson

使い方

  • 第一引数にJson化したいオブジェクトインスタンスを渡す
  • 第二引数のprettyPrintはJsonを読みやすく整形するかどうか(デフォルトfalse:整形しない)

  • 対象オブジェクトのpublicフィールドがシリアライズされる

  • プロパティは対象外

  • privateフィールドをシリアライズに含みたい場合は[SerializeField]をつける

  • publicフィールドをシリアライズから除外したい場合は[NonSerialized]をつける

注意点

  • DictionaryやHashtableのような連想配列はシリアライズ対象外

使用例(シンプルにシリアライズする)

Json化したいクラス
[Serializable] //なくても一応変換はされるが、一部挙動が変になるので必ずつけておくべき
class Data
{
    public int publicField = 123;
    public float publicFloat = float.MaxValue;
    public bool publicBool = true;
    public string publicString = "ABC";

    //NonSerializedをつけるとシリアライズされない
    [NonSerialized]
    public int ignoreFiled = 999;

    //配列にも対応
    public int[] publicIntArray = new[] { 1, 2, 3, 4, 5 };

    //プロパティはシリアライズされない
    public string publicProperty { get { return "Property"; } }

    //privateフィールドはシリアライズされない
    private int privateField = 123;

    //privateも含めたい場合は SerializeFieldAttribute をつける
    [SerializeField]
    private int privateField2 = 345;
}
ToJson
var data = new Data();
var json = JsonUtility.ToJson(data,true); //整形する
結果
{
    "publicField": 123,
    "publicFloat": 3.4028234663852887e38,
    "publicBool": true,
    "publicString": "ABC",
    "publicIntArray": [
        1,
        2,
        3,
        4,
        5
    ],
    "privateField2": 345
}

使用例(DictionaryをJsonにする)

Dictionaryはそのままではシリアライズしてくれません。
そのため、ISerializationCallbackReceiverを実装しシリアライズ/デシリアライズにHookして手作業でKeyとValueのリストに変換する必要があります。マジかよ

Dictionaryをシリアライズする
[Serializable]
class Data2 : ISerializationCallbackReceiver
{
    //Dictionaryはシリアライズしてくれない
    public Dictionary<int, string> playerList;

    [SerializeField]
    private List<int> _keyList ;
    [SerializeField]
    private List<string> _valueList;

    public Data2()
    {
        playerList = new Dictionary<int, string>();
        playerList.Add(1, "A");
        playerList.Add(2, "B");
        playerList.Add(3, "C");
    }

    /// <summary>
    /// Json化時に実行してくれる
    /// </summary>
    public void OnBeforeSerialize()
    {
        //シリアライズする際にkeyとvalueをリストに展開
        _keyList = playerList.Keys.ToList();
        _valueList = playerList.Values.ToList();
    }

    /// <summary>
    /// Jsonからデシリアライズされたあとに実行される
    /// </summary>
    public void OnAfterDeserialize()
    {
        //デシリアライズ時に元のDictionaryに詰め直す
        playerList = _keyList.Select((id, index) =>
        {
            var value = _valueList[index];
            return new {id, value};
        }).ToDictionary(x => x.id, x => x.value);

        _keyList.Clear();
        _valueList.Clear();

    }
}

Json → Object

Jsonからオブジェクトに変換する

JsonUtility.FromJson

使い方

  • FromJsonの引数またはジェネリックで変換先の型を指定する

注意点

  • 変換に失敗した場合はSystem.ArgumentExceptionが飛ぶ
  • 要素がjson中に存在しない場合はデフォルト値が設定される(0,false,nullなど)
  • 引数なしのコンストラクタを強制的に呼び出してインスタンス化する(引数ありのコンストラクタのみ定義して初期化している場合は注意!)

使用例

デシリアライズ先のオブジェクト
[Serializable]
class Data3
{
    public int  intValue;
}
FromJson
//json
var json = "{\"intValue\":123}";

//ジェネリック使わない版
var data = JsonUtility.FromJson(json, typeof(Data3)) as Data3;
//ジェネリックで型パラメータ渡す版
var data2 = JsonUtility.FromJson<Data3>(json);

json中に要素があったかどうか区別したい場合どうするのか

デシリアライズ時、対象のフィールドが値型(intとかfloatとか)であり、かつその要素が含まれていなかった場合、その要素の値はデフォルト値に設定されてしまう。

これを明確に、「jsonに対象の要素が含まれていなかったかどうか」を判断したい場合はnull許容型を使う。
追記:null許容型は値が定義されていようが全てnullになってしまうみたいです。内容が間違っていました。申し訳ありません。

もしJsonUtility単体で要素の有無まで確認したい場合は、nullを表現するためのクラスを自分で定義し、該当の要素を一段ネストさせるなどの対応が必要です。(LitJsonならnull許容型を使えばOKなんですけどね)

自前でNullを表現できるようにクラスを作って構造体を包む
[Serializable]
class Data4
{
   public NullableInt hogeValue;
}

[Serializable]
class NullableInt
{
   public int Value;
}
Json例
{"hogeValue": null} //未定義の場合はnullになる
{"hogeValue": {"Value": 10}} //ネストして表現する

既存のインスタンスをjsonの値で上書きする

JsonUtility.FromJsonOverwrite

使用例

デシリアライズ先の型
[Serializable]
private class Data5
{
    public int intValue;
    public int intValue2;

    public Data5(int i1,int i2)
    {
        intValue = i1;
        intValue2 = i2;
    }
}
FromJsonOverwriteで一部上書き
var data = new Data5(100, 200);
var json = "{\"intValue\":300}";

//Jsonで上書き
JsonUtility.FromJsonOverwrite(json, data);

Debug.Log(data.intValue); // 300 ←上書きされている
Debug.Log(data.intValue2); // 200

おまけ(ネストしたクラスの場合)

シリアライズ

クラスがネストしている場合、JsonUtility.ToJsonでは変換してくれません ~~これもマジかよ!
[Serializable]をつけたらネストしていてもシリアライズ・デシリアライズできました。必ずつけないとダメですね。

変換対象
/// <summary>
/// meta情報を含んだJson
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
class JsonData<T>
{
    public Meta meta;
    public T data;

    public JsonData(string metaName,T data)
    {
        this.meta = new Meta(metaName);
        this.data = data;
    } 
}

/// <summary>
/// Meta情報の部分
/// </summary>
[Serializable]
class Meta
{
    public string typeName;
    public Meta(string typeName){this.typeName = typeName;}
}

/// <summary>
/// 実際に変換したいデータ
/// </summary>
[Serializable]
class PlayerParams
{
    public string name;
    public int hp;
    public int attackPower;

    public PlayerParams(string name,int hp,int attack)
    {
        this.name = name;
        this.hp = hp;
        this.attackPower = attack;
    }
}
期待するjson
{
    "data": {
        "attackPower": 300, 
        "hp": 100, 
        "name": "FighterA"
    }, 
    "meta": {
        "typeName": "player_param"
    }
}
結果
var p1 = new PlayerParams("FighterA", 100, 300);
var jsonData = new JsonData<PlayerParams>("player_param", p1);

// 結果: {"meta":{"typeName":"player_param"},"data":{"name":"FighterA","hp":100,"attackPower":300}}
Debug.Log(JsonUtility.ToJson(jsonData));

デシリアライズ

同様にFromJsonもネストしていた場合はデシリアライズしてくれない
こちらも[Serializable]をつけたらできました

var json = "{\"meta\":{\"typeName\":\"player_param\"},\"data\":{\"name\":\"FighterA\",\"hp\":100,\"attackPower\":300}}";

var x1 = JsonUtility.FromJson<JsonData<PlayerParams>>(json);
Debug.Log(x1.meta.typeName); // player_param
Debug.Log(x1.data.name); // FighterA
Debug.Log(x1.data.attackPower); // 300
Debug.Log(x1.data.hp); //100

おまけ2(連想配列に変換する)

結論から言えばできない

[Serializable]
class Data6
{
    public int a = 1;
    public string b = "hoge";
}
var d = new Data6();
var json = JsonUtility.ToJson(d);

var x = JsonUtility.FromJson<Dictionary<string, object>>(json);
//Debug.Log(x["a"]); //keyが存在しない
//Debug.Log(x["b"]);//keyが存在しない

//ちなみにLitJsonだとできる
var x2 = LitJson.JsonMapper.ToObject<Dictionary<string, object>>(json);
Debug.Log(x2["a"]); //1
Debug.Log(x2["b"]); //"hoge"

//そもそもLitJsonは型未指定の場合はJsonDataという連想配列っぽいコレクションに変換される
var x3 = LitJson.JsonMapper.ToObject(json);