JsonUtility.ToJson は引数の値を書き換えることがある

Unity 罠シリーズ第一弾。

Unity で JSON フォーマットを扱う際の主要な選択肢である JsonUtility ですが、注意点があるのでそのお話。

挙動を確認してみる

まず、Character という以下のようなデータクラスがあるとします。

using System;

[Serializable]
public class Character
{
    public int id = -1;

    public string name = "仮の名前";

    public override string ToString()
    {
        return $"{name} (ID: {id}) です。";
    }
}

これを外部とやり取りするため JsonUtility.ToJson で JSON に変換するとします。その前後で何か起きるか見てみましょう。

using UnityEngine;

public class JsonUtilitySample : MonoBehaviour
{
    private void Start()
    {
        Character[] characters = new Character[]
        {
            new Character()
            {
                id = 1,
                name = "Hoge",
            },
            new Character()
            {
                id = 2,
                name = "Fuga",
            },
            new Character()
            {
                id = 3,
                name = "Piyo",
            },
            null,
        };

        Debug.Log("変換を通す前の characters");
        for (int i = 0; i < characters.Length; i++)
        {
            Debug.Log(characters[i]);
        }

        string json = JsonUtility.ToJson(characters);

        Debug.Log("変換を通した後の characters");
        for (int i = 0; i < characters.Length; i++)
        {
            Debug.Log(characters[i]);
        }
    }
}

上のコードで変換を通す前後で、出しているログに変化があるでしょうか?
あるはずがないですよね。

変換を通す前の characters
Hoge (ID: 1) です。
Fuga (ID: 2) です。
Piyo (ID: 3) です。
Null

変換を通した後の characters
Hoge (ID: 1) です。
Fuga (ID: 2) です。
Piyo (ID: 3) です。
Null

予想通りです。

では少し変えて Party というデータクラスが追加され、それで外部とやり取りするようになったとします。

using System;

[Serializable]
public class Character
{
    public int id = -1;

    public string name = "仮の名前";

    public override string ToString()
    {
        return $"{name} (ID: {id}) です。";
    }
}

[Serializable]
public class Party
{
    public int id = 0;

    public Character[] members = null;
}
using UnityEngine;

public class JsonUtilitySample : MonoBehaviour
{
    private void Start()
    {
        Character[] characters = new Character[]
        {
            new Character()
            {
                id = 1,
                name = "Hoge",
            },
            new Character()
            {
                id = 2,
                name = "Fuga",
            },
            new Character()
            {
                id = 3,
                name = "Piyo",
            },
            null,
        };

        Party party = new Party()
        {
            id = 1,
            members = characters,
        };


        Debug.Log("変換を通す前の party.members");
        for (int i = 0; i < party.members.Length; i++)
        {
            Debug.Log(party.members[i]);
        }

        string json = JsonUtility.ToJson(party);

        Debug.Log("変換を通した後の party.members");
        for (int i = 0; i < party.members.Length; i++)
        {
            Debug.Log(party.members[i]);
        }
    }
}

上のコードで変換を通す前後のログはどうなるでしょう。まぁ、変わらないでしょうね。

変換を通す前の party.members
Hoge (ID: 1) です。
Fuga (ID: 2) です。
Piyo (ID: 3) です。
Null

変換を通した後の party.members
Hoge (ID: 1) です。
Fuga (ID: 2) です。
Piyo (ID: 3) です。
仮の名前 (ID: -1) です。

!?

はい、Null だった 4 つ目が new Character() 的なものになってしまいました。
おそらく Unity のシリアライズ周りの挙動が関係していると思われますが、それでもこの挙動は想定しないという方は多いと思われます。

結論

ということで、JsonUtility.ToJson に渡した引数は使い回さない方がいいというお話でした。
基本的に JSON に変換するタイミングは外部に出す直前だと思うので、これが問題になることはほとんどないと思いますが頭の片隅に入れておくと役に立つ日が来るかもしれません。

コメント

タイトルとURLをコピーしました