依赖:xxf_json
反序列化兼容特征一览表
类型\是否兼容 |
int |
double |
num |
string |
bool |
---|---|---|---|---|---|
int |
yes |
yes |
yes |
yes |
yes |
double |
yes |
yes |
yes |
yes |
yes |
num |
yes |
yes |
yes |
yes |
yes |
string |
yes |
yes |
yes |
yes |
yes |
bool |
yes |
yes |
yes |
yes |
yes |
专业词语
.g.dart : 是json_annotation生成的中间解析文件
DTO : 网络传输模型,这里泛指json解析模型
中间件
对json提供如下基础中间件,两种兼容模式,一种给全日志
JsonConverter 兼容基本类型(int,num,double,string,bool),异常情况解析成对应类型的默认值,达到同级别js,oc等语言层兼容
nullable_converter 兼容基本类型(int,num,double,string,bool),异常情况解析成null,需要DTO声明字段为可空类型
strict_converter 先兼容解析,解析不了再报错,解决json解析报错,不提示具体内容,导致一个一个去比较DTO里声明的字段,或者打印stack才可以排查具体字段
至于要使用多少类型兼容和什么策略,请自己选,上图只是模版
用法
DTO层增加注解 通过 @JsonSerializable 注解参数converters 注入!
其中 primitiveConvertors :基本类型解析器,安全处理,如遇到失败会转换成默认值 primitiveNullableConvertors:基本类型解析器,安全处理,如遇到失败变成null,适合声明可空类型的字段
primitiveStrictConvertors:基本类型解析器,不安全处理,用于提示内容,比原错误始信息增加 内容本身到日志里面,解决原始报错,不提示具体内容,不好分析DTO的那个字段
用法代码示例:
@JsonSerializable(
converters: [...primitiveNullableConvertors, RRuleJsonAdapter()])
class Event{
}
字段级别增加注解
通过 @JsonKey fromJson 注解增加参数
用法代码示例:
@JsonSerializable()
class Event {
//类型不对,就解析成""双引号字符串
@JsonKey(fromJson: StringDecoder.decodeOrEmpty)
String? eventId; //设备内日程id
}
提供更自由的控制,每种类型都提供三种策略
bool_decoder.dart
double_decoder.dart
int_decoder.dart
num_decoder.dart
string_decoder.dart
class StringDecoder {
///策略1 异常情况解析为空
static String? decodeOrNull(dynamic json) {
if (json == null) return null;
return json.toString();
}
///策略2 异常情况解析默认值,比@JsonKey(defaultValue: "")更健壮
static String decodeOrEmpty(dynamic value) {
return decodeOrNull(value) ?? "";
}
///策略3 尝试解析异常情况报错并给出错误值
static String decodeOrException(dynamic value) {
return decodeOrNull(value) ??
(throw BaseDecoder.createParseError("String", value));
}
}
框架的中间件优先级是 @JsonKey(fromJson)>@JsonSerializable(converters)
推荐倾向
1. DTO里字段声明成可空字段
给DTO增加可空兼容转换器@JsonSerializable( converters: primitiveNullableConvertors),
具体字段的应用由业务层来处理兼容, 这样不至于整个页面出问题,个别字段的问题,交由service层/repo层来校验参数,
eg.如在版本迭代中 枚举的类型可能增加,但是之前版本代码是没有的,那么非空类型就出问题了
实在要坚持后端一定不会变,其他页面传进来的参数也不会变, 那么就选择primitiveStrictConvertors或者decodeOrException 会尝试解析之后再报错出来!
规范
结合convertor和 fromJson,toJson Api 注入到DTO身上,不应该去手写解析 手写/或者手改.g.dart 合并代码和以及兼容性都有些问题
枚举的兼容
枚举默认按名字,如按其他值解析,有如下三种方式 1. 在枚举值上添加@JsonValue(value)
自定义jsonDecoder 这样枚举在其他DTO声明的地方都可以快速适配,
在其他DTO 声明枚举的字段上增加注解 @JsonKey(fromJson: StatusConverter.fromJson, toJson: StatusConverter.toJson)
不要在其他DTO解析的地方(其他DTO有声明这个枚举类型字段) 来手写if判断!
/// 日历账号类型
enum CalDavTypeEnum {
google("google"),
iCloud("iCloud"),
calDAV("CalDAV");
final String value;
const CalDavTypeEnum(this.value);
}
/// 枚举值和 JSON 数据的映射关系
Map<String, CalDavTypeEnum> _stringToEnum =
CalDavTypeEnum.values.associateBy((e) => e.value);
Map<CalDavTypeEnum, String> _enumToString =
CalDavTypeEnum.values.associate((e) => MapEntry(e, e.value));
class CalDavTypeEnumDecoder {
static CalDavTypeEnum decode(dynamic json) {
return _stringToEnum["$json"] ??
(throw ArgumentError('Unknown enum value: $json'));
}
static CalDavTypeEnum? decodeOrNull(dynamic json) {
return _stringToEnum["$json"];
}
}
class CalDavTypeEnumEncoder {
static String encode(CalDavTypeEnum? myEnum) {
return _enumToString[myEnum] ??
(throw ArgumentError('Unknown enum: $myEnum'));
}
static String? encodeOrNull(CalDavTypeEnum? myEnum) {
return _enumToString[myEnum];
}
}
///解析转换器
class CalDavTypeEnumJsonConverter
extends JsonConverter<CalDavTypeEnum, dynamic> {
const CalDavTypeEnumJsonConverter();
@override
CalDavTypeEnum fromJson(json) {
return CalDavTypeEnumDecoder.decode(json);
}
@override
toJson(CalDavTypeEnum object) {
return CalDavTypeEnumEncoder.encode(object);
}
}
///解析转换器 可空
class CalDavTypeEnumNullableJsonConverter
extends JsonConverter<CalDavTypeEnum?, dynamic> {
const CalDavTypeEnumNullableJsonConverter();
@override
CalDavTypeEnum? fromJson(json) {
return CalDavTypeEnumDecoder.decodeOrNull(json);
}
@override
toJson(CalDavTypeEnum? object) {
return CalDavTypeEnumEncoder.encodeOrNull(object);
}
}
json_annotation使用指引
模型增加 @JsonSerializable注解
在模型声明文件头部增加 part 'xxx.g.dart'; 其中xxx 一般是模型名字,当然也可以是其他名字
字段如有必要增加@JsonKey
在命令行 cd 到模型对应的目录
在命令行执行 flutter pub run build_runner build
那么就能看到生成 xxx.g.dart文件的生成,将这个文件添加到git
然后再模型里面声明方法引用
part 'account_xxx.g.dart';
@JsonSerializable()
class AccountInfo {
int? id;
//声明反序列化方法
factory AccountInfo.fromJson(Map<String, dynamic> json) =>
_$AccountInfoFromJson(json);
///声明序列化方法
Map<String, dynamic> toJson() => _$AccountInfoToJson(this);
}