json_serializable
json_serializable提供了几种策略1来处理泛型类型,如单个对象T
或List<T>
(从v. 5.0.2+开始):
- 辅助类:
JsonConverter
- 辅助方法:
@JsonKey(fromJson:, toJson:)
- 泛型参数工厂
@JsonSerializable(genericArgumentFactories: true)
1 我所知道的。可能还有其他方法。
辅助类:JsonConverter
基本思路:编写一个自定义的JsonConverter
类,具有fromJson
和toJson
方法,以识别和处理我们的类型T
字段的反/序列化。
JsonConverter
策略的好处是它将所有模型的反/序列化逻辑封装到一个可重用的单个类中,该类可在需要序列化相同模型类型的任何类之间重用。而且,与泛型参数工厂策略不同,您的toJson
,fromJson
调用不会改变,其中每个toJson
,fromJson
调用都需要我们提供一个处理函数。
我们可以通过注释来使用JsonConverter
对我们的对象进行反/序列化:
- 需要自定义处理的单个
T
/ List<T>
字段,或
- 整个类(在这种情况下,将在所有类型为
T
的字段上使用)。
以下是一个包含泛型类型字段T
的json_serializable类OperationResult<T>
的示例。
OperationResult
类的注意事项:
- 具有单个泛型类型字段
T t
。
t
可以是类型为T
的单个对象或对象的List<T>
。
- 无论
T
是什么类型,它都必须具有toJson()/fromJson()
方法(即可反/序列化)。
- 具有名为
ModelConverter
的JsonConverter
类,注释了T t
字段。
- 生成的存根
_$OperationResultFromJson<T>(json)
和_$OperationResultToJson<T>()
现在需要一个T
变量。
@JsonSerializable(explicitToJson: true)
class OperationResult<T> {
final bool ok;
final Operation op;
@ModelConverter()
final T t;
final String title;
final String msg;
final String error;
OperationResult({
this.ok = false,
this.op = Operation.update,
required this.t,
this.title = 'Operation Error',
this.msg = 'Operation failed to complete',
this.error= 'Operation could not be decoded for processing'});
factory OperationResult.fromJson(Map<String,dynamic> json) =>
_$OperationResultFromJson<T>(json);
Map<String,dynamic> toJson() => _$OperationResultToJson<T>(this);
}
以下是用于上述内容的 JsonConverter
类 ModelConverter
:
class ModelConverter<T> implements JsonConverter<T, Object> {
const ModelConverter();
@override
T fromJson(Object json) {
if (json is Map<String,dynamic>) {
if (json.containsKey('items') && json.containsKey('customer')) {
return Order.fromJson(json) as T;
}
if (json.containsKey('status') && json.containsKey('menuItem')) {
return OrderItem.fromJson(json) as T;
}
if (json.containsKey('name') && json.containsKey('restaurantId')) {
return Menu.fromJson(json) as T;
}
if (json.containsKey('menuId') && json.containsKey('restaurantId')) {
return MenuItem.fromJson(json) as T;
}
} else if (json is List) {
if (json.isEmpty) return [] as T;
Map<String,dynamic> _first = json.first as Map<String,dynamic>;
bool _isOrderItem = _first.containsKey('status') && _first.containsKey('menuItem');
if (_isOrderItem) {
return json.map((_json) => OrderItem.fromJson(_json)).toList() as T;
}
bool _isMenuItem = _first.containsKey('menuId') && _first.containsKey('restaurantId');
if (_isMenuItem) {
return json.map((_json) => MenuItem.fromJson(_json)).toList() as T;
}
}
throw ArgumentError.value(json, 'json', 'OperationResult._fromJson cannot handle'
' this JSON payload. Please add a handler to _fromJson.');
}
@override
Object toJson(T object) {
if (object is Serializable){
return object.toJson();
}
else if (object is List) {
if (object.isEmpty) return [];
if (object.first is Serializable) {
return object.map((t) => t.toJson()).toList();
}
}
throw ArgumentError.value(object, 'Cannot serialize to JSON',
'OperationResult._toJson this object or List either is not '
'Serializable or is unrecognized.');
}
}
以下是用于我们的模型类(如Order和MenuItem)实现的Serializable接口(请参见上面的ModelConverter中的toJson()代码,了解如何以及为什么使用它):
abstract class Serializable {
Map<String,dynamic> toJson();
}
辅助方法: @JsonKey(fromJson:, toJson:)
该注释用于为使用json_serializable的类中的任何类型的字段指定自定义de/serialization处理程序,而不仅仅是通用类型。
因此,我们可以使用与上面JsonConverter示例中使用的"查看键"逻辑相同的方式,为我们的通用类型字段T t
指定自定义处理程序。
下面,我们在我们的类OperationResultJsonKey<T>
中添加了两个静态方法(仅在此Stackoverflow示例中命名为此以明显):
(这些也可以作为顶级函数存在。)
然后我们将这两个方法提供给JsonKey:
@JsonKey(fromJson: _fromJson, toJson: _toJson)
然后,在重新运行我们的flutter或dart build_runner之后(flutter pub run build_runner build
或dart run build_runner build
),这两个静态方法将由json_serializable提供的生成的de/serialize方法使用。
@JsonSerializable(explicitToJson: true)
class OperationResultJsonKey<T> {
final bool ok;
final Operation op;
@JsonKey(fromJson: _fromJson, toJson: _toJson)
final T t;
final String title;
final String msg;
final String error;
OperationResultJsonKey({
this.ok = false,
this.op = Operation.update,
required this.t,
this.title = 'Operation Error',
this.msg = 'Operation failed to complete',
this.error = 'Operation could not be decoded for processing'});
static T _fromJson<T>(Object json) {
}
static Object _toJson<T>(T object) {
}
factory OperationResultJsonKey.fromJson(Map<String, dynamic> json) =>
_$OperationResultJsonKeyFromJson(json);
Map<String, dynamic> toJson() => _$OperationResultJsonKeyToJson(this);
}
通用的参数工厂 @JsonSerializable(genericArgumentFactories: true)
在这种专门处理 de/serialization 的方式中,我们需要直接为在 OperationResult
上调用 toJson()
和 fromJson()
提供自定义的 de/serialization 方法。
这种策略可能是最灵活的(允许您为每种泛型类型指定序列化处理方式),但它也非常冗长,需要您在每个 toJson
/ fromJson
调用上提供一个序列化处理函数。这很快就会变得烦人。
toJson
例如,在序列化 OperationResult<Order>
时,.toJson()
调用需要一个函数,告诉 json_serializable 如何在序列化 OperationResult<Order>
时序列化 Order
字段。
该助手函数的签名为:Object Function(T) toJsonT
所以在 OperationResult
中,我们的 toJson()
存根方法(由 json_serializable 为我们完成)从:
Map<String,dynamic> toJson() => _$OperationResultToJson(this);
变成:
Map<String,dynamic> toJson(Object Function(T) toJsonT) => _$OperationResultToJson<T>(this, toJsonT);
toJson()
从接受 零个 参数变为接受一个 函数 作为参数
- 当序列化
Order
时,json_serializable 将调用该 函数
- 该 函数 返回的是
Object
而不是 Map<String,dynamic>
,以便它还可以处理 List
中的多个 T
对象,例如 List<OrderItem>
fromJson
对于我们的 OperationResult<Order>
类上使用的 genericArgumentFactories
的 fromJson()
部分,我们需要提供一个签名为:T Function(Object?) fromJsonT
的函数。
因此,如果我们要对具有泛型类型的对象进行 de/serialization,例如 OperationResult<Order>
,则我们的 fromJson()
助手函数将是:
static Order fromJsonModel(Object? json) => Order.fromJson(json as Map<String,dynamic>);
这里有一个使用 genericArgumentFactories
的示例类 OperationResultGAF
:
@JsonSerializable(explicitToJson: true, genericArgumentFactories: true)
class OperationResultGAF<T> {
final bool ok;
final Operation op;
final String title;
final String msg;
final T t;
final String error;
OperationResultGAF({
this.ok = false,
this.op = Operation.update,
this.title = 'Operation Error',
this.msg = 'Operation failed to complete',
required this.t,
this.error= 'Operation could not be decoded for processing'});
factory OperationResultGAF.fromJson(Map<String,dynamic> json, T Function(Object? json) fromJsonT) =>
_$OperationResultGAFFromJson<T>(json, fromJsonT);
Map<String,dynamic> toJson(Object Function(T) toJsonT) =>
_$OperationResultGAFToJson<T>(this, toJsonT);
}
如果
T
是一个名为
Order
的类,那么这个
Order
类可以保存用于与genericArgumentFactories一起使用的静态辅助方法。
@JsonSerializable(explicitToJson: true, includeIfNull: false)
class Order implements Serializable {
static Order fromJsonModel(Object? json) => Order.fromJson(json as Map<String,dynamic>);
static Map<String, dynamic> toJsonModel(Order order) => order.toJson();
factory Order.fromJson(Map<String,dynamic> json) => _$OrderFromJson(json);
Map<String,dynamic> toJson() => _$OrderToJson(this);
}
请注意,上述辅助方法仅调用由json_serializable生成的常规
toJson()
、
fromJson()
存根方法。
将这些静态方法添加到模型类中的目的是使提供这些辅助方法给
OperationResultGAF.toJson()
、
OperationResultGAF.fromJson()
更加简洁:我们只提供它们的函数名称而不是实际函数。
例如,我们可以使用以下方式代替原有的写法:
OperationResultGAF<Order>.fromJson(_json, (Object? json) => Order.fromJson(json as Map<String,dynamic>));
我们可以使用:
OperationResultGAF<Order>.fromJson(_json, Order.fromJsonModel);
如果
T
是包括
List<MenuItem>
这样的对象,则我们需要处理列表的帮助方法。
以下是一些静态的帮助方法的示例,可添加到
MenuItem
类中以处理列表:
static List<MenuItem> fromJsonModelList(Object? jsonList) {
if (jsonList == null) return [];
if (jsonList is List) {
return jsonList.map((json) => MenuItem.fromJson(json)).toList();
}
if (jsonList is Map<String,dynamic>) {
return [MenuItem.fromJson(jsonList)];
}
throw ArgumentError.value(jsonList, 'jsonList', 'fromJsonModelList cannot handle'
' this JSON payload. Please add a handler for this input or use the correct '
'helper method.');
}
static List<Map<String,dynamic>> toJsonModelList(Object list) {
if (list is List<MenuItem>) {
return list.map((item) => item.toJson()).toList();
}
return [];
}
以下是关于如何在单元测试中调用这些静态帮助方法的示例:
List<MenuItem> _mListA = [MockData.menuItem1, MockData.menuItem2];
OperationResultGAF<List<MenuItem>> _orC = OperationResultGAF<List<MenuItem>>(
op: Operation.delete, t: _mListA);
var _json = _orC.toJson(MenuItem.toJsonModelList);
OperationResultGAF<List<MenuItem>> _orD = OperationResultGAF<List<MenuItem>>.fromJson(
_json, MenuItem.fromJsonModelList);
expect(_orC.op, _orD.op);
expect(_orC.t.first.id, _orD.t.first.id);