JsonUtilityについて

Unity5.3からJsonUtilityという機能が追加されました。
オブジェクトをJSONにすることとJSONからオブジェクトを作る・値を入れることができます。JSONはオブジェクトを文字列で表現する方法のことであり、単純な仕組みでエンコードされるために広く使われているフォーマットです。C#ではxmlやバイナリの形式にオブジェクトをシリアライズ(ファイルに保存できるような形式にすること)できますが、JSONはライブラリを使わないと使えません。
Unityで公式にサポートしてくれると使いやすくなるので、データの持ち方に1つ選択肢が増えたことになります。

JSONの形式は文字列であるので、拡張性が高く柔軟だというのが特徴だと思います。あるオブジェクトAをJSONにして、別のオブジェクトBにデシリアライズ(オブジェクトに戻すこと)が可能です。なぜならJSONが文字列であり、データに何が含まれているのかを表しているだけだからです。

これと異なる特性をもつのがバイナリです。データをメモリ上での表現をそのまま出力したような機械にとってわかりやすく、人間にわかりにくい表現です。
バイナリ形式(BinaryFormatterを使った場合)は保存したオブジェクトと同じ型でデシリアライズしないとエラーになったり、バイナリにはフィールドがあるのに復元するクラスにはフィールドがなくなった場合にもエラーが起きます。これはBinaryFormatterで作ったデータになんらかの型情報が含まれていることを表しているでしょう。データがどういったクラスにより作られ、どういった要素が含まれているかがデータにあるために変更すると一致しなくなるのです。(どういった条件でデシリアライズできなくなるかまで調べていませんが、基本的にはフィールドの変更はしない方が良いと思います。)
バイナリ形式はクラスの型を変更できないデメリットがありますが、文字列の解釈がないため一般的に変換が高速です。バイナリ形式の場合で将来拡張するなら、クラスに余分なフィールドを入れておくことが一番良い対策になります。余りに多くの使わないパラメータがあると無駄が大きいので、どれくらいの容量を取るかは判断が難しいところです。

JSONを使う方が頻繁にアップデートが存在するアプリには合っているので、これを使っていた方が困らないことが多いでしょう。
JsonUtilityは現在のところ型を利用する方法のみが使えるようです。JSONにシリアライズ、JSONからデシリアライズするためには変換用にクラスや構造体が必要だということです。 (intの配列とかをそのまま渡してもシリアライズできないです。)JSONに対してそのままデータを検索したり、データを追加したりするのは将来的にはあり得ると思います。(ドキュメントに「The JSON Serializer does not currently support working with ‘unstructured’ JSON」とあります。currentlyなので将来的にはサポートする気がします。)


JsonUtility.ToJsonはそこまで注意することはないです。publicなフィールドをJSON化してくれます。NonSerialized属性をつけるとそのフィールドは無視されます。JSON化されるものが何かはインスペクタ―上で表示されているものと一致しているようです。例えば[SerializeField]がついたprivateなフィールドはJSON化されます。また逆に[System.NonSerialized]をつけるとpublicなフィールドでもJSON化されなくなります。
ToJsonの第一引数はオブジェクトを渡します。

[System.Serializable]
public class Test
{
    [SerializeField]
    private int a = 1;
    public int b = 2;
    public int[] c = { 12345 };
    public float f = 0.22f;
}

というクラスを定義して、これを作りJSONにしてみます。

Test test = new Test();
var json_test = JsonUtility.ToJson(test);

というようにすると、json_testには以下の文字列が入っています。

fの値が0.22に一致しないのは誤差のせいでしょう。JSONのデータはわかりやすい構造をしていて、データが正しいかのデバッグがしやすいです。

第ニ引数にtrueを渡すと、下のように見やすく出力されます。改行とかインデントのための空白やタブは情報を持っていないので、情報としては無駄です。保存する場合やプログラム内でのやり取りにはPrettyPrintは使わないようにし、デバッグ用に使うのが良いです。


JSONからオブジェクトを作るのは2通りあります。
JsonUtility.FromJsonJsonUtility.FromJsonOverwriteです。

Test obj = JsonUtility.FromJson<Test>(json);

のようにすると、文字列jsonからTestクラスが作られます。使い方はシンプルなのですが、与える文字列jsonには注意が必要です。文字列はJsonUtility.ToJson等を使って同じクラスを表すJSONを使うのが通常でしょうが、特に制限はないです。全く異なるクラスから作られたJSONを渡したり、自分で文字列を作って渡しても問題ありません。
そういった場合、余分なフィールドや型の異なるフィールドが出てくるでしょう。FromJsonはうまくできているようで、これらのものはすべて無視されます。FromJsonは型パラメータに渡したクラスのシリアライズできるフィールドを対象にし、JSONからフィールドの名前と型が一致したものだけ解釈しています。
対応するものがないフィールドについては注意が必要で、このときフィールドはデフォルト値にされます。(例えばintだったら0です。)

Unity5.3.4f1で試したところ、対応するものがなくてもデフォルト値にされてないように思います。マニュアルが正しいのか実装が正しいのかわかりませんがバグです。

[System.Serializable]
public class Test2
{
    public int a = 0;
    public int c = 5;
}

というクラスを新たに作り、

Test test = new Test();
var json = JsonUtility.ToJson(test);
print("json:" + json);
var from_json = JsonUtility.FromJson<Test2>(json);
print("from_json:" + JsonUtility.ToJson(from_json));

のように処理を書いてみました。 Test型のインスタンスをJSONにしたものを出力し、そのJSONからTest2のインスタンスを作っています。作ったTest2型のインスタンスのフィールドがどうなっているか知るため、JSONで出力しています。
実行した結果は以下のようになりました。

jsonにはcというフィールドがないので、Test2としてデシリアライズするときにcがデフォルト値0になるかと思ったのですが、そうはなりませんでした。JsonUtilityのバグかドキュメントのミスかも知れません。

JsonUtility.FromJsonOverwriteはデシリアライズするJSONに復元しようとするクラスの対応するフィールドがない場合、そのフィールドには何もしません。
上に述べたようにJsonUtility.FromJsonでも何故かフィールドの値が変更されてないため、現状ではインスタンスの生成の有無で使い分けることになると思います。

コメント

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