flutter json解析增强
依赖: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 CalDavTypeEnumJsonConverterextends JsonConverter<CalDavTypeEnum, dynamic> {const CalDavTypeEnumJsonConverter();@overrideCalDavTypeEnum fromJson(json) {return CalDavTypeEnumDecoder.decode(json);}@overridetoJson(CalDavTypeEnum object) {return CalDavTypeEnumEncoder.encode(object);}
}///解析转换器 可空
class CalDavTypeEnumNullableJsonConverterextends JsonConverter<CalDavTypeEnum?, dynamic> {const CalDavTypeEnumNullableJsonConverter();@overrideCalDavTypeEnum? fromJson(json) {return CalDavTypeEnumDecoder.decodeOrNull(json);}@overridetoJson(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);
}