怒りのISerializationCallbackReceiver

はいどーもベスト・オブ・ニートのツミオですこんばんは。
みなさんJsonUtility使ってますか?
僕はセーブデータを保存するとき使っているのですが、こいつがちょっとした曲者です。
本日はそんなお話です。

JsonUtilityってなに

JsonUtilityとはズバリ「JSONデータを操作するためのユーティリティ関数」です。
例えばSerializable属性をつけたクラスのSerializeField属性がついたフィールドを簡単にJSON形式の文字列へ変換できます。
逆にT型のJSON形式の文字列からT型のインスタンスを作成することもできます(あるいはインスタンスの上書き)。
詳しくはドキュメントをご覧ください。

JsonUtilityのここがムカツク

便利なJsonUtilityですが、実際に使ってみると以下のことでイラッとしました。

・インターフェイスはきちんとシリアライズできない
・HashSet<T>どころかDictionary<TKey, TValue>すらサポートしていない
・というかジェネリック型はList以外ダメ?
・DateTime構造体もダメ

なんでもJsonUtilityは高速らしいので、そのせいで色々と制約があるんでしょうね。
まあ仕方がないのでその対策を考えてみました。

インターフェイス

これはシンプルで、保存したいデータにはインターフェイスを含めないことにしました。
コマンドパターンなんかを駆使したデータの保存は不可ということです。
あるいは、ひと手間かける必要があります。

DateTime

いったん文字列に変換してから保存しました。
チャンチャン。

Dictionary<TKey, TValue>

Dictionary<TKey, TValue>型をどうシリアライズするか悩んだのですが、ISerializationCallbackReceiverインターフェイスを使用することで解決できました。
このインターフェイスを実装すると、シリアライズ前とデシリアライズ後に任意の処理を加えることができます。
なので

・Dictionary<TKey, TValue>型のインスタンスを経由してList<T>型のインスタンスにシリアライズ
・デシリアライズ時は逆に、List<T>型へデシリアライズされた中身をDictionary<TKey, TValue>型に変換する処理を加える

とすることで無理やりセーブデータにDictionary<TKey, TValue>型を含めました。
でもぶっちゃけ面倒オブ面倒なので、別のライブラリ使ったほうがいいですかね……。

あ、ISerializationCallbackReceiverインターフェイスの詳細はドキュメントをどうぞ。

HashSet<T>

ISerializationCallbackReceiverを知る前だったので、List<T>型のフィールドを持つ新しいクラス(HashSetのように使える)を作って対応しました。
ISerializationCallbackReceiverを使ったものに直してもいいんですが、まあ面倒だし動いてるしなのでこのまま行きます。
とはいえ独自実装は怖いし、単純に不便でもあるのでやめたい気もしますが……。

終わりに

JsonUtility便利なんですけどねえ……。
びみょーに融通がきかない箇所があってちょっと困ります。
これってセーブデータを保存するときに使うのは想定してないんでしょうかね……。
よくわかりません。

ほなそんな感じでまた。
あ、お仕事も募集中です。

フォローする