やりたいこと
toJson, fromJson 時に、そのままの値でマッピングするのでなく特殊な処理を行い変換したい。
基本
@JsonKey
内に fromJson
, toJson
に変換用の関数を渡してあげればそれ使ってくれる。
static な関数しか渡せない。
以下は timestamp を datetime にマッピングする例
@JsonKey(
fromJson: timestampToDatetimeNullable,
toJson: datetimeTotimestampNullable,
)
DateTime? createdAt,
// 以下は変換用の関数たち
/// TimestampをDateTimeに変換する(nullable)
DateTime? timestampToDatetimeNullable(Timestamp? timestamp) {
return timestamp?.toDate();
}
/// DateTimeをTimestampに変換する(nullable)
Timestamp? datetimeTotimestampNullable(DateTime? dateTime) {
if (dateTime == null) return null;
return Timestamp.fromDate(dateTime);
}
使いまわす
JsonConverter を継承すればアノテーションとして使いまわせる
class TimestampField
implements JsonConverter<DateTime?, Timestamp?> {
const TimestampField();
@override
DateTime? fromJson(Timestamp? timestamp) {
return timestamp?.toDate();
}
@override
Timestamp? toJson(DateTime? dateTime) {
if (dateTime == null) return null;
return Timestamp.fromDate(dateTime);
}
}
@TimestampField() DateTime? createdAt,
よくあるエラー
fromJson
の変換用関数で List<特定の型>
を引数にすると死ぬ。
特定の型があっていたとしても死ぬ。
例えば以下を読み込んで、userID のリストから User のリストにデコードしようとする。
{
"id": 0,
"name": "ほげチーム",
"userIDs": [1, 2]
}
// 省略
@JsonKey(
name: 'userIDs',
fromJson: userDecode,
toJson: userEncode,
)
List<User> users,
// 省略
// デコード用
List<User> _userDecode(List<int> userIDs) {
return userIDs.map((id) => User.fromID(id));
}
すると List<dynamic>
は List<int>
に変換できないと言われて死ぬ。
さて、変換してる様子は g.dart に書いてある。変換してからデコード関数に渡すようだ。
users: _userDecode(json['userIDs'] as List<int>),
で、これはだめらしい。json.decode とか、firebase からのデータは dynamic
であり、dynamic
は List<dynamic>
, Map<String, dynamic>
までしかダウンキャストできない。そして中身が正しくてもList<dynamic> -> List<int>
とかはできない。ただし、dynamic -> int
はできる。
なのでこうする。dynamic
を map
で一つずつ int
に変換してあげる。
// 省略
@JsonKey(
fromJson: userDecode,
toJson: userEncode,
)
List<User> users,
// 省略
// デコード用
List<User> _userDecode(List<dynamic> userIDs) {
return userIDs.map((dynamic id) => id as int).map((id) => User.fromID(id));
}