4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Unity#JsonUtilityのデシリアライズ時の挙動確認とデシリアライズ失敗時にエラーを出させる方法

Last updated at Posted at 2021-01-10

概要

UnityのJsonUtilityをサーバーレスポンスのデシリアライザとして使う際実用に耐えるかどうか確かめた際のログです。
めっちゃ適当で恐縮ですけど、以下のデシリアライズがちゃんとできるか確かめたものになります。

JSONとクラスのプロパティが一致

うまくいった。まぁこれうまくいかないわけないよねぇ。

[Serializable]
public class Test {
    [SerializeField] private string aaa;
    [SerializeField] private int bbb;

    public override string ToString() {
        return $"aaa:{aaa}, bbb:{bbb}";
    }
}
var json = "{\"aaa\":\"test1\", \"bbb\":3 }";
var test = JsonUtility.FromJson<Test>(json);
Debug.Log(test); // aaa:test1, bbb:3

リストを含んだクラスのデシリアライズ

うまくいった。

[Serializable]
public class Test {
    [SerializeField] private string aaa;
    [SerializeField] private List<string> bbb;

    public override string ToString() {
        return $"aaa:{aaa}, bbb:{string.Join(",", bbb)}";
    }
}
var json = "{\"aaa\":\"test1\", \"bbb\":[\"a\",\"b\",\"c\"]}";
var test = JsonUtility.FromJson<Test>(json);
Debug.Log(test); // aaa:test1, bbb:a,b,c

オブジェクトがネストしたクラスのデシリアライズ

これもうまくいく。

[Serializable]
public class Test {
    [SerializeField] private string aaa;
    [SerializeField] private Test2 bbb;

    public override string ToString() {
        return $"aaa:{aaa}, bbb:[{bbb}]";
    }
}
[Serializable]
public class Test2 {
    [SerializeField] private string ccc;
    [SerializeField] private List<string> ddd;

    public override string ToString() {
        return $"ccc:{ccc}, ddd:{string.Join(",", ddd)}";
    }
}
var json = "{\"aaa\":\"test1\", \"bbb\":{\"ccc\": \"test2\", \"ddd\": [\"a\",\"b\",\"c\"]}}";
var test = JsonUtility.FromJson<Test>(json);
Debug.Log(test); // aaa:test1, bbb:[ccc:test2, ddd:a,b,c]

Dictionary型を含んだクラスのデシリアライズ

うまくいかない。 まぁ、これは他の記事でも読んでたから想定内。
ネストしたクラスがデシリアライズできるなら問題はないかな。
(ただエラーが出ないのはちょっと問題だな…)

[Serializable]
public class Test {
    [SerializeField] private string aaa;
    [SerializeField] private Dictionary<string, string> bbb;

    public override string ToString() {
        if (bbb == default) {
            return $"aaa:{aaa}, bbb:[]";
        }
        return $"aaa:{aaa}, bbb:[{string.Join(",", bbb.Select(a => a.Key + ":" + a.Value))}]";
    }
}
var json = "{\"aaa\":\"test1\", \"bbb\":{\"ccc\": \"test2\", \"ddd\": \"test3\"}}";
var test = JsonUtility.FromJson<Test>(json);
Debug.Log(test); // aaa:test1, bbb:[]

JSONとクラスの型が違う場合

うまくいかない。 ただエラーにならず一致しない箇所が空やnullになる。
これもエラー吐いてくれないのねん…

[Serializable]
public class Test {
    [SerializeField] private string aaa;
    [SerializeField] private string bbb;

    public override string ToString() {
        return $"aaa:{aaa}, bbb:{bbb}";
    }
}
var json = "{\"aaa\":\"test1\", \"bbb\":{\"ccc\": \"test2\", \"ddd\": \"test3\"}}";
var test = JsonUtility.FromJson<Test>(json);
Debug.Log(test); // aaa:test1, bbb:

JSONとクラスのプロパティが一致しない場合

これもエラーは吐かれない。うーむ…。

[Serializable]
public class Test {
    [SerializeField] private string aaa;
    [SerializeField] private string bbb;

    public override string ToString() {
        return $"aaa:{aaa}, bbb:{bbb}";
    }
}
var json = "{\"aaa\":\"test1\", \"ccc\":\"test2\"}";
var test = JsonUtility.FromJson<Test>(json);
Debug.Log(test); // aaa:test1, bbb:

ジェネリクスを使ったクラスのデシリアライズ

これはうまくいった。よかった。
これ上手くいかなかったら使用見送るとこだった…

[Serializable]
public class Test<T> {
    [SerializeField] private string aaa;
    [SerializeField] private T bbb;

    public override string ToString() {
        return $"aaa:{aaa}, bbb:[{bbb}]";
    }
}

[Serializable]
public class Test2 {
    [SerializeField] private string ccc;
    [SerializeField] private string ddd;

    public override string ToString() {
        return $"ccc:{ccc}, ddd:{ddd}";
    }
}
var json = "{\"aaa\":\"test1\", \"bbb\":{\"ccc\":\"test2\", \"ddd\":\"test3\"}}";
var test = JsonUtility.FromJson<Test<Test2>>(json);
Debug.Log(test); // aaa:test1, bbb:[ccc:test2, ddd:test3]

結論

ギリ使えそう。
ただ、パラメータ不一致の場合に空文字だったりnullになったりするから何かしら必須の箇所はNullチェックする方法は考えないといけなさそう。

Nullチェックしてエラーを吐くようにする

こんな感じの作れば、デシリアライズする際にnullを許容しない変数がnullや空文字の場合、エラーにすることはできる。
もうちょい調整すればサーバーレスポンスのデシリアライザとして使えそうな気がしなくもない。

// カスタムアトリビュート作成
[System.AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = true)]
public class NotNull : Attribute {
}

// これを使ってデシリアライズする
public class JSONDeserializer {
    public static T FromJSON<T>(string json) {
        var res = JsonUtility.FromJson<T>(json);
        var _type = res.GetType();
        // とりあえずprivateな変数だけ。publicも見たりする場合多分ここの処理は変更が必要になる
        var fields = _type.GetFields( BindingFlags.NonPublic | BindingFlags.Instance);
        foreach (var field in fields) {
            var attr = field.GetCustomAttribute(typeof(NotNull));
            if (attr == null) {
                continue;
            }

            var val = field.GetValue(res);

            if (val == null || string.IsNullOrEmpty(val.ToString())) {
                throw new Exception("えらーだよ");
            }
        }
        return res;
    }   
}

[Serializable]
public class Test {
    
    [SerializeField][NotNull] private string aaa;
    [SerializeField][NotNull] private string bbb;

    public override string ToString() {
        return $"aaa:{aaa}, bbb:[{bbb}]";
    }
}

var json = "{\"aaa\":\"test1\", \"bbb\":{\"ccc\":\"test2\", \"ddd\":\"test3\"}}";
var test = JSONDeserializer.FromJSON<Test>(json); //ここでException「えらーだよ」がthrowされる。
Debug.Log(test); // ここまで到達しない
4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?