継承関係にあるクラスをJSONでシリアライズ・デシリアライズする場合の方法について。
使うパッケージはjson_serializable。
ググるとシリアライズについてはヒットするのだけど、デシリアライズは見つけることができず、とりあえず自分でやってみた。
実装後、しつこくググったらdart_json_mapperというのでできることが分かり、そちらについては継承関係にあるクラスのデシリアライズの注意点だけ記述しておく。
ここで取り扱ったのは、以下の様なクラス構造のもの。
ListClass内でList<ClassA>を保持しているといったケース。
json_serializable
多分、標準的なJSONのシリアライズ・デシリアライズのパッケージなのだと思う。
継承関係にあるクラスをデシリアライズする場合に、ちょっと特殊な方法を用いないとできなかった。
サンプルとして作成したソースは上の場所に配置している。
これで作成したクラスをシリアライズした結果は次のようになる。
{"list":[{"title":"hoge","count":5},{"title":"hage","count":5},{"title":"fuga","count":3,"value":2.0,"type":"classb"}]}
解説
継承関係にあるクラスをデシリアライズしたい場合「何のクラスを作成するのか」という情報がないと作れない。
単純にjson_serializableで自動生成されたシリアライズ・デシリアライズ処理を使っただけでは、すべてClassAにデシリアライズされてしまう。
そこで、JSONファイル内に何のクラスを生成するのかというクラスタイプを書き込み、それを元に派生クラスを生成するといった手段を取ることにする。
(この方法自体はdart_json_mapperでも同様だったので、ありふれた方法だとおもう)
シリアライズ
クラスの情報をJSONに書き込むのは以下の様にしている。
Map<String, dynamic> toJson() {
final result = _$ClassBToJson(this);
result[ClassA.classType] = className;
return result;
}
ClassA.classTypeはクラス識別のためのJSONのタグ名で「type」を設定している。classNameはClassBのstatic変数として定義しているクラスの名前。
これでtoJsonで書き込むことで、"type":"classb"
という情報が出力されることになる。
デシリアライズ
List<ClassA>という型で読み込もうとしているため、ClassAのデシリアライズで型の名前による生成ファクトリーを切り替える処理が必要になる。
static Map<String, ClassA Function(Map<String, dynamic>)> factorymap = {};
factory ClassA.fromJson(Map<String, dynamic> json) {
if (!json.containsKey(classType)) {
return _$ClassAFromJson(json);
} else {
return factorymap[json[classType] as String]!(json);
}
}
これが上の様な実装。
factorymapはクラス名とそれに対応する生成用のファクトリーを保存するマップで、ClassA.fromJson内で、クラスを示すタグが来たらそれに合わせたファクトリを選択、使用するといったものになっている。
当然、上だけの実装では派生クラスを生成できるわけではなく、factorymapに派生クラス側のfromJsonを追加する必要がある。
そちらはClassB側に用意しておき、main処理等で必要とする派生クラスの登録を行わなければいけない。
static void register() {
ClassA.factorymap[className] = ClassB.fromJson;
}
上のがClassB側で用意した登録メソッドで、ClassB.register()を呼び出す。
dart_json_mapper
json_serializableでデシリアライズの実装を行った後、ググったら上のパッケージを見つけてしまった。たぶんこれがあるから継承関係にあるクラスのデシリアライズの質問がなかったのかな?
サンプルや実行方法などについてはある程度そろっているのでそちら参照してもらう方が速いかもしれない。
使用上の注意点
ここで記述するのは、継承関係にあるクラスのシリアライズ・デシリアライズに関する注意点。
このような関係のクラスでも処理可能かなと思ったのだけど、実装してデシリアライズを行ったら例外が発生してしまった。
こんな感じに、abstractなクラスを基点としないとだめだった。
dart_json_mapperページのサンプルとして、クラスの識別としてenumを使っているけど、こちらはenum以外でも行けるようだ。
私が作成したサンプルでStringを使用したが、問題なくデシリアライズできた。
コメント