携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情
大家好,这篇文章向大家介绍在 Flutter 中使用 json_serializable 解析 JSON 时,如何使用枚举 Enum
JSON 数据
首先,我们先看一段 JSON 数据:
[
{
"type": 1,
"text": "大家好"
},
{
"type": 2,
"image": {
"url": "xxx",
"height": 800,
"width": 400
}
},
{
"type": 3,
"audio": {
"url": "xxx"
}
}
]
类似上面这段 JSON 数据,在我们实际开发中非常常见:比如订单有不同的状态,信息流中不同的类型展示不同的布局。这时如果使用枚举,可以提升代码的可读性,也便于后续维护。
定义 Model
上面那段 JSON 数据,其中的 type 字段就可以使用枚举 Enum,我们可以定义如下的模型类:
import 'package:json_annotation/json_annotation.dart';
import 'audio_model.dart';
import 'image_model.dart';
part 'message_model.g.dart';
@JsonSerializable()
class MessageModel {
@JsonKey(
defaultValue: MessageType.unknown, unknownEnumValue: MessageType.unknown)
final MessageType type;
@JsonKey(defaultValue: '')
final String text;
final ImageModel? image;
final AudioModel? audio;
const MessageModel({
required this.type,
required this.text,
this.image,
this.audio,
});
factory MessageModel.fromJson(Map<String, dynamic> json) =>
_$MessageModelFromJson(json);
Map<String, dynamic> toJson() => _$MessageModelToJson(this);
}
@JsonEnum(valueField: "type")
enum MessageType {
unknown(-1),
text(1),
image(2),
audio(3);
final int type;
const MessageType(this.type);
}
上面代码中 MessageType 就是我们定义的枚举类,json_serializable 解析程序可以把 JSON 数据中的 1、2、3 分别映射为 MessageType.text、MessageType.image、MessageType.audio 枚举值
JsonEnum.valueField 是 json_serializable 新版本新增了一个的属性,可以用来取代原来的@JsonValue() ,我们看一下生成后代码:
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'message_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
MessageModel _$MessageModelFromJson(Map<String, dynamic> json) => MessageModel(
type: $enumDecodeNullable(_$MessageTypeEnumMap, json['type'],
unknownValue: MessageType.unknown) ??
MessageType.unknown,
text: json['text'] as String? ?? '',
image: json['image'] == null
? null
: ImageModel.fromJson(json['image'] as Map<String, dynamic>),
audio: json['audio'] == null
? null
: AudioModel.fromJson(json['audio'] as Map<String, dynamic>),
);
Map<String, dynamic> _$MessageModelToJson(MessageModel instance) =>
<String, dynamic>{
'type': _$MessageTypeEnumMap[instance.type]!,
'text': instance.text,
'image': instance.image,
'audio': instance.audio,
};
const _$MessageTypeEnumMap = {
MessageType.unknown: -1,
MessageType.text: 1,
MessageType.image: 2,
MessageType.audio: 3,
};
从生成后的代码中可以发现,json_serializable 会生成一个 Map ,通过这个 Map 可以把枚举值和服务端返回的 int 类型的值进行关联。
测试用例
下面我们通过一些测试用例来测试上面的模型类
import 'dart:convert';
import 'package:flutter_test/flutter_test.dart';
import 'package:json_demo/message_model.dart';
void main() {
test('test enum type 1', () {
String str = """
{
"type": 1,
"text": "大家好"
}
""";
MessageModel model = MessageModel.fromJson(json.decode(str));
expect(model.type, MessageType.text);
expect(model.text, '大家好');
});
test('test enum type 2', () {
String str = """
{
"type": 2,
"image": {
"url": "xxx",
"height": 800,
"width": 400
}
}
""";
MessageModel model = MessageModel.fromJson(json.decode(str));
expect(model.type, MessageType.image);
expect(model.image?.url, 'xxx');
});
test('test enum type 3', () {
String str = """
{
"type": 3,
"audio": {
"url": "yyy"
}
}
""";
MessageModel model = MessageModel.fromJson(json.decode(str));
expect(model.type, MessageType.audio);
expect(model.audio?.url, 'yyy');
});
test('test enum type -1', () {
String str = """
{
"type": -1
}
""";
MessageModel model = MessageModel.fromJson(json.decode(str));
expect(model.type, MessageType.unknown);
});
test('test enum type 4', () {
String str = """
{
"type": 4
}
""";
MessageModel model = MessageModel.fromJson(json.decode(str));
expect(model.type, MessageType.unknown);
});
test('test enum type none', () {
String str = """
{
"text": "hello"
}
""";
MessageModel model = MessageModel.fromJson(json.decode(str));
expect(model.type, MessageType.unknown);
});
运行单元测试代码,测试用例可以全部通过
注意事项
@JsonKey(defaultValue: MessageType.unknown, unknownEnumValue: MessageType.unknown) final MessageType type;
在 MessageModel 中,可以看到,MessageType type 被定义为非空类型,同时使用了 JsonKey 的两个属性配置,defaultValue 和 unknownEnumValue,那么为什么要同时设置两个属性呢
我们可以把这两个属性全部删掉,运行单元测试,这时有两个测试用例会报错
测试用例 test enum type 4 报错信息如下:
Invalid argument(s): `4` is not one of the supported values: -1, 1, 2, 3
测试用例 test enum type none 报错信息如下:
Invalid argument(s): A value must be provided. Supported values: -1, 1, 2, 3
默认值
当我们添加 defaultValue: MessageType.unknown 时
class MessageModel {
@JsonKey(defaultValue: MessageType.unknown)
final MessageType type;
}
test('test enum type none', () {
String str = """
{
"text": "hello"
}
""";
MessageModel model = MessageModel.fromJson(json.decode(str));
expect(model.type, MessageType.unknown);
});
这时 test enum type none 便不再报错,因为 JSON 中没有 type 这个参数,这时 type 应该是 null,而我们把 MessageType type 定义为非空类型,那就需要设置一个默认值来处理 null 的情况。
未知枚举值
当我们添加 unknownEnumValue: MessageType.unknown 时
class MessageModel {
@JsonKey(unknownEnumValue: MessageType.unknown)
final MessageType type;
}
test('test enum type 4', () {
String str = """
{
"type": 4
}
""";
MessageModel model = MessageModel.fromJson(json.decode(str));
expect(model.type, MessageType.unknown);
});
test enum type 4 便不再报错,这是因为 type = 4 不是枚举类 MessageType 中定义的值,这时,就需要用到 unknownEnumValue 属性,它可以处理未识别的枚举值。
总结
在我们项目开发中,如果使用到枚举类时,我们也是按照上面讲的来做的,现在总结如下:
- 首先,我们在定义枚举类时,会定义一个 unknown 枚举值;
- 解析 JSON 的 Model 类中,把枚举类字段设置为非空类型,方便调用者,不需要做非空判断;
- 设置
defaultValue属性,主要是为了容错处理; - 设置
unknownEnumValue属性,主要是为了后续升级,老版本容错处理