うにてぃブログ

主にUnityとC#に関する記事を書いていきます

【Unity】JsonUtility の罠

JsonUtility

Unity には Json データを操作するクラスとして JsonUtility がある
JsonUtility は Primitive 型、Array、シリアライズ可能なクラス・構造体を Json 形式に変換できる

using System;
using UnityEditor;
using UnityEngine;

[Serializable]
public class JsonTest
{
    public int IntVal;
    public string StrVal;
    public Vector2 Vector2Val;
    public Vector3Int Vector3IntVal;
    public int[] IntArray;
    public JsonTestSub SubClass;
}

[Serializable]
public class JsonTestSub
{
    public int IntVal;
    public string StrVal;
}

private static void ToJson()
{
    var test = new JsonTest
    {
        IntVal = 10,
        StrVal = "str",
        Vector2Val = Vector2.one,   
        Vector3IntVal = Vector3Int.left,
        IntArray = new []{ 0, 1, 2},
        SubClass = new JsonTestSub
        {
            IntVal = 100,
            StrVal = "str",
        }       
    };
    Debug.Log(JsonUtility.ToJson(test, true));
}

例えば上記のコードを実行すると下記のJsonが取得できる

{
    "IntVal": 10,
    "StrVal": "str",
    "Vector2Val": {
        "x": 1.0,
        "y": 1.0
    },
    "Vector3IntVal": {
        "x": -1,
        "y": 0,
        "z": 0
    },
    "SubClass": {
        "IntVal": 100,
        "StrVal": "str"
    },
    "IntArray": [
        0,
        1,
        2
    ]
}

クラスに戻す場合は JsonUtility.FromJson<JsonTest>(json) を利用する

意図していなかった挙動

先程の Json を作成する処理から JsonTestSubクラス生成処理を削除しToJson を行ってみる

private static void ToJson()
{
    var test = new JsonTest
    {
        IntVal = 10,
        StrVal = "str",
        Vector2Val = Vector2.one,   
        Vector3IntVal = Vector3Int.left,
        IntArray = new []{ 0, 1, 2},
    };
    Debug.Log(JsonUtility.ToJson(test, true));
}

これにより出力された Json は以下になる

{
    "IntVal": 10,
    "StrVal": "str",
    "Vector2Val": {
        "x": 1.0,
        "y": 1.0
    },
    "Vector3IntVal": {
        "x": -1,
        "y": 0,
        "z": 0
    },
    "IntArray": [
        0,
        1,
        2
    ],
    "SubClass": {
        "IntVal": 0,
        "StrVal": ""
    }
}

見て分かるように SubClass のインスタンスを作成していない (null) にも関わらず、SubClass の Json が出力されている
ToJson の呼び出し前後で SubClass のインスタンスを確認してみると
呼び出し前だと null なのに対し、呼び出し後では null では無くなっているため
JsonUtility.ToJson 内で対象クラスのクラスフィールドが null の場合内部でインスタンスを作成していると考えられる

private static void ToJson()
{
    var test = new JsonTest
    {
        IntVal = 10,
        StrVal = "str",
        Vector2Val = Vector2.one,   
        Vector3IntVal = Vector3Int.left,
        IntArray = new []{ 0, 1, 2},
    };

    Debug.Log(test.SubClass == null); // true
    Debug.Log(JsonUtility.ToJson(test, true));
    Debug.Log(test.SubClass == null); // false
}

not null の場合処理するような場合、ログ等でJsonUtlity を利用していると クラス内部のフィールド値がすべて default 値になってしまうので注意が必要です

注意

上記は Unity 2018.4.14f1 で確認しており、Unity 2019.2.9f1 で確認したところ ToJson した際に SubClassJson に含まれていたがインスタンスは null のままだったため、おそらく Unity 2019.1~ では修正された不具合だと思われる

参考サイト

UnityEngine.JsonUtility - Unity スクリプトリファレンス