【Unity】JsonUtilityの基本的な使い方と内部挙動

こじゃらこじゃら

ゲーム中のデータをJsonとして簡単に扱う方法を知りたいの。

このはこのは

JsonUtilityクラスを使えばこの辺が楽に実現できるわ。

JsonUtilityクラスUnity標準のJSONデータ操作用のユーティリティクラスです。

プログラム上のオブジェクトをJSON形式の文字列にシリアライズしたり、逆にJSON形式の文字列からオブジェクトにデシリアライズする機能を有します。

これにより、例えば次のようなことが手軽に実現できるようになります。

JsonUtilityの活用例
  • JSON形式のセーブデータとして読み書きする
  • JSON形式でサーバーとデータのやり取りをする

特に後者は、様々なサービスのAPIでもよく採用される方法と言えるでしょう。 [1]

また、JsonUtilityクラスのシリアライズ・デシリアライズでは、内部的にUnityのシリアライザが用いられており、シリアライズ可能な条件や挙動などもこれに準拠します。

本記事では、このようなJsonUtilityクラスの機能の使い方を解説するとともに、内部挙動や使用上の注意点についても触れます。

動作環境
  • Unity 2022.1.7f1

スポンサーリンク

JSON形式にシリアライズする

JsonUtility.ToJsonメソッドを用います。

2種類のオーバーロードされたメソッドが用意されています。

public static string ToJson(object obj);
public static string ToJson(object obj, bool prettyPrint);

第1引数objは共通で、シリアライズ対象のオブジェクトを指定します。

2つ目のメソッドの第2引数prettyPrintは、trueなら改行やインデントが有効、falseなら無効になります。

第2引数が指定されない場合、prettyPrintにfalseが指定されたのと同じ挙動となります。

サンプルスクリプト

以下、自分自身のクラスをJSON文字列に変換してログ出力するサンプルスクリプトです。

ToJsonExample.cs
using UnityEngine;

public class ToJsonExample : MonoBehaviour
{
    // 文字列フィールド
    [SerializeField] private string _stringField;

    // ベクトルフィールド
    [SerializeField] private Vector3 _vectorField;

    // カスタムクラス
    // シリアライズさせるためには[System.Serializable]属性が必須
    [System.Serializable]
    private class CustomClass
    {
        public Vector3 position;
        public Quaternion rotation;
    }

    // カスタムクラスのフィールド
    [SerializeField] private CustomClass _customClassField;

    private void Start()
    {
        // 自身のインスタンスをJSONにシリアライズ
        // 第2引数trueでJSONを見やすく整形する
        var json = JsonUtility.ToJson(this, true);

        // JSONの内容をログ出力
        Debug.Log(json);
    }
}

上記スクリプトをToJsonExample.csという名前でUnityプロジェクトに保存し、適当なゲームオブジェクトにアタッチすると機能するようになります。

例では以下のようにインスペクターに適当な値を設定することとします。

実行結果

以下のようにスクリプトのフィールドの内容がJSON出力されます。

上記のrotationフィールドはインスペクター上ではオイラー角で指定するようになっていましたが、JSONにシリアライズされるとクォータニオンの要素として出力されていることが分かります。

スクリプトの解説

以下部分で自分自身のインスタンスをJSON形式にシリアライズした文字列を取得しています。

// 自身のインスタンスをJSONにシリアライズ
// 第2引数trueでJSONを見やすく整形する
var json = JsonUtility.ToJson(this, true);

例では第2引数にtrueを指定することでインデントと改行されたJSON文字列を取得していますが、ここをfalseにすると以下のようにインデントと改行の無いJSON文字列が得られます。

データ容量の削減ができるため、ファイル保存やサーバー通信など用途で活用すると良いでしょう。

JSON文字列をオブジェクトにデシリアライズする

2つのメソッドが提供されています。

public static T FromJson(string json);
public static void FromJsonOverwrite(string json, object objectToOverwrite);

第1引数にはデシリアライズされるJSON形式の文字列を指定します。

デシリアライズされたオブジェクトは、FromJsonメソッド新しいインスタンスを返し、FromJsonOverwriteメソッド第2引数に指定されたオブジェクトに上書きする形で返します。

後者のFromJsonOverwriteメソッドは、MonoBehaviour継承クラスなど直接インスタンス化できないものや、インスタンス化されることによって発生するGC.Allocを抑えたい場面などで役立ちます。

サンプルスクリプト

以下、JSON文字列をデシリアライズしてログ出力するサンプルスクリプトです。

FromJsonExample.cs
using UnityEngine;

public class FromJsonExample : MonoBehaviour
{
    // テスト用のJSON文字列
    // フィールド名はダブルコーテーション「"」で囲む必要がある
    private const string JsonText = "{\"text\":\"TestValue\", \"position\":{\"x\":1,\"y\":2,\"z\":3}}";

    // デシリアライズ後の情報を格納するクラス
    [System.Serializable]
    private class TestClass
    {
        public string text;
        public Vector3 position;
    }

    private void Start()
    {
        // JSON文字列をデシリアライズ
        var obj = JsonUtility.FromJson<TestClass>(JsonText);

        // デシリアライズしたオブジェクトの内容をログ出力
        Debug.Log($"text = {obj.text}, position = {obj.position}");
    }
}

スクリプトの内容をFromJsonExample.csという名前でUnityプロジェクトに保存し、適当なゲームオブジェクトにアタッチすると機能します。

実行結果

フィールドにデシリアライズされた値が格納されていることが確認できました。

スクリプトの解説

以下部分でJSON形式の文字列をクラス型に変換しています。

// JSON文字列をデシリアライズ
var obj = JsonUtility.FromJson<TestClass>(JsonText);

シリアライズ・デシリアライズ時の内部挙動

JsonUtilityクラスは内部ではUnityのシリアライザが用いられています。

そのため、シリアライズされるフィールドはUnityシリアライザのシリアライズ条件に準拠します。

フィールドがシリアライズ条件は以下の通りです。

シリアライズ条件
  • publicか[SerializeField]属性が付加されている
  • staticでない
  • constでない
  • readonlyでない
  • シリアライズ可能な型である

最後の条件であるシリアライズ可能な型は以下の通りです。

シリアライズ可能な型
  • int、float、stringなどのプリミティブ型
  • Enum型
  • Vector2、AnimationCurveなどの特定のUnityビルトイン型
  • [Serializable]属性の付いた独自classやstruct
  • 配列やリスト(List<T>)など

詳細な条件は以下リファレンスのページをご覧ください。

具体例など含めたUnityシリアライザの挙動については以下記事でも解説していますので、必要ならご覧ください。

使用上の注意点

いくつか嵌ると思われる落とし穴について紹介しておきます。

structなどの値型はボックス化される

JsonUtility.ToJsonメソッドやJsonUtility.FromJsonなどでやり取りする際は、シリアライズ対象のオブジェクトをobject型にキャストする必要があります。

そのため、structなどの値型に対してシリアライズ・デシリアライズするとボックス化が発生し、GC.Allocが生じます。

頻繁な呼び出しを行わなければ問題ないと思われますが、留意しておくと安心かもしれません。

MonoBehaviourをデシリアライズするときはFromJsonOverwriteメソッドを使う

MonoBehaviourインスタンスをシリアライズするときはJsonUtility.ToJsonメソッドで問題なく行えますが、デシリアライズするときはJsonUtility.FromJsonOverwriteメソッドを使う必要があります。

JsonUtility.FromJsonメソッドを使ってデシリアライズしようとした場合、MonoBehaviourクラスのインスタンスを直接作成しようとしてしまうために次のようなエラーが発生します。

エラーとなるコード例

// MyMonoBehaviour型はMonoBehaviour継承クラス
var obj = JsonUtility.FromJson<MyMonoBehaviour>(JsonText);

出力結果

ArgumentException: Cannot deserialize JSON to new instances of type 'MyMonoBehaviour.'
UnityEngine.JsonUtility.FromJson (System.String json, System.Type type) (at <5ffb408b6e684788aa0f23105f42e325>:0)
UnityEngine.JsonUtility.FromJson[T] (System.String json) (at <5ffb408b6e684788aa0f23105f42e325>:0)

そのため、MonoBehaviourインスタンスをデシリアライズしたい場合、GameObject.AddComponentメソッドなどで一度インスタンス化してから、JsonUtility.FromJsonOverwriteメソッドで上書きでデシリアライズする必要があります。

正しい例

// 最初にコンポーネントを追加しておく
var obj = gameObject.AddComponent<MyMonoBehaviour>();

// 後から上書きでデシリアライズ
JsonUtility.FromJsonOverwrite(JsonText, obj);

パフォーマンスについて

JsonUtilityクラスはUnityシリアライザを通じてシリアライズ・デシリアライズが行われますが、パフォーマンスに関しては.NETが提供するものより大幅に高速です。

また、GC.Allocが最小限に抑えられるような設計になっていることも特徴です。

例えばJsonUtility.ToJsonメソッドなら、戻り値として返す文字列生成に対してのみGC.Allocが発生します。

JsonUtility.FromJsonOverwriteメソッドは、上書きされるstring型や配列型フィールドに対してGC.Allocが発生してしまいますが、完全な値型への上書きでは全くGC.Allocが発生しません。

さいごに

JsonUtilityクラスを使うことで、手軽にJSON形式のシリアライズ・デシリアライズ操作ができます。

機能が少ない分、パフォーマンスが最適化されているメリットがあり、通常のJSONを扱う用途であればJsonUtilityクラスを使えば良いでしょう。

関連記事

参考サイト

スポンサーリンク