【Unity】JsonUtilityとEditorJsonUtilityの違いをちゃんと理解する

UnityのJsonUtilityとEditorJsonUtilityの違いについてまとめました。

Unity2019.4.0

JsonUtility?

JsonUtilityはUnityにおけるJsonリアライゼーションのためのユーティリティクラスです。
以下のようにするとインスタンスJsonシリアライズできます。

using System;
using UnityEngine;

public class JsonUtilityExample : MonoBehaviour
{
    private void Start()
    {
        var instance = new SerializableClass(111, "test");
        
        // Jsonにする
        var json = JsonUtility.ToJson(instance);
        // {"_testInt":111,"_testString":"test"}
        Debug.Log(json);

        // Jsonから戻す
        var deserialized = JsonUtility.FromJson<SerializableClass>(json);
    }
}

[Serializable]
public class SerializableClass
{
    [SerializeField] private int _testInt;
    [SerializeField] private string _testString;

    public SerializableClass(int testInt, string testString)
    {
        _testInt = testInt;
        _testString = testString;
    }
}

UnityではこのJsonUtilityの他にEditor専用のEditorJsonUtilityクラスが用意されています。
この二つは似ていますが微妙に挙動が異なるので本記事ではその違いについてまとめます。

UnityEngine.Objectをシリアライズできるか

さてまず一つ目の違いはUnityEngine.Objectの派生クラスをシリアライズできるかどうかです。
この挙動を見るためにまず適当なPrefabをJsonUtilityとEditorJsonUtilityでそれぞれシリアライズしてみます。

using UnityEditor;
using UnityEngine;

public class JsonUtilityExample : MonoBehaviour
{
    [SerializeField] private GameObject _prefab;
    
    private void Start()
    {
        var json = JsonUtility.ToJson(this);
        // {"_prefab":{"instanceID":17002}}
        Debug.Log(json);

        var editorJson = EditorJsonUtility.ToJson(this);
        // ※Prefabの部分のみ抜粋
        // "_prefab":{"fileID":8041828765868915304,"guid":"4d6436b75c9d13c40be6298ad4b25ebb","type":3}
        Debug.Log(editorJson);
    }
}

上記の結果を見ると、JsonUtilityだとPrefabのInstanceIDがシリアライズされているのに対し、
EditorJsonUtilityではGUIDとFileIDがシリアライズされています。

JsonUtilityがシリアライズしているInstanceIDはUnityを再起動すると変わってしまいます。
これに対してEditorJsonUtilityはアセット毎に不変のGUIDを保持しています。

このようなことからUnityEngine.ObjectをシリアライズするにはEditorJsonUtilityを使う必要があります。
ちなみに当然ですがデシリアライズもEditorJsonUtilityで行う必要があります。

バックグランドスレッドで使えるかどうか

JsonUtilityはUnityのAPIにしては珍しくメインスレッド以外のスレッドでも使えます。
これに対してEditorJsonUtilityはメインスレッドでしか使えません。ややこしいです。

試しに以下の処理を書いてみるとEditorJsonUtilityの方だけ例外が発生することが確認できます。

using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;

public class JsonUtilityExample : MonoBehaviour
{
    [SerializeField] private int _testInt;
    
    private async void Start()
    {
        await Task.Run(() =>
        {
            var json = JsonUtility.ToJson(this);
            // {"_testInt":0}
            Debug.Log(json);
        });

        await Task.Run(() =>
        {
            // UnityException: ToJsonInternal can only be called from the main thread.
            var editorJson = EditorJsonUtility.ToJson(this);
            Debug.Log(editorJson);
        });
    }
}

ちなみにJsonUtilityでUnityEngine.Objectの派生クラスをバックグラウンドスレッドでシリアライズしようとすると、
Assertion failed on expression: 'CurrentThreadIsMainThread()'という謎のアサーションが発生します。

using System.Threading.Tasks;
using UnityEngine;

public class JsonUtilityExample : MonoBehaviour
{
    [SerializeField] private GameObject _prefab;
    
    private async void Start()
    {
        await Task.Run(() =>
        {
            // Assertion failed on expression: 'CurrentThreadIsMainThread()'
            var json = JsonUtility.ToJson(this);
            Debug.Log(json);
        });
    }
}

JsonUtilityはバックグラウンドスレッドで実行できるといいつつこれを出すのはどうかと思いますが、
前節の通りJsonUtilityはUnityEngine.Object非対応なのでまあそういうことなのでしょう。

インターフェースの違い

JsonUtilityとEditorJsonUtilityのインターフェースには微妙に違いがあります。
まずJsonUtilityでデシリアライズする際には、オブジェクトを返すFromJson()とオブジェクトを渡して中身を書き換えてもらうFromJsonOverwrite()が存在します。

var obj = JsonUtility.FromJson(json);
JsonUtility.FromJsonOverwrite(json, obj);

これに対してEditorJsonUtilityにはFromJsonOverwrite()しか存在しません。

EditorJsonUtility.FromJsonOverwrite(json, obj);

参考

docs.unity3d.com

docs.unity3d.com