Flutter json 2 model with Built Value
Flutter json 2 model with Built Value
Flutter中json转换model, 除了手动转之外, 就是利用第三方库做一些代码生成.
流行的库有: json_serializable和built_value
本文介绍built_value的实际使用及问题处理.
Flutter中的json转model方法
Flutter中json到model类型的转换可以有多种方式:
- 利用官方自带的dart convert中的json解码. 该方法只能将json转换为List或Map, 剩下的工作需要手动完成, 根据key取值赋值给model的字段.
- 利用第三方的库, 做代码生成, 流行的库有: json_serializable和built_value. 原理都是相同的, 先写一些模板代码, 说明一下model是什么样子的, 然后运行命令行生成一些代码, 之后就可以很方便地调用, 将json转换为model了.
使用json_serializable可以看:
- 官网的例子: Serializing JSON using code generation libraries.
- Flutter实战中文的: 11.7 Json转Dart Model类
本篇文章主要介绍built value的使用.
built value使用指南
实例: 用github api拿到的events: https://api.github.com/events?per_page=10
如何转化成model对象呢?
TDD
先写个测试, 明确一下我们想要的目标.
在test
下建立一个文件, 比如叫json_test.dart
.
里面写main函数和两个测试:
void main() {
test("parse events list", () {
const jsonString = """replace with events list json string""";
expect(Event.fromEventsListJson(jsonString).first.id, "11732023561");
});
test("parse event", () {
const jsonString = """replace with event json string""";
expect(Event.fromJson(jsonString).id, "11732036753");
});
}
这里面应该放json字符串的, 太长了我就省略了, 这样看比较清晰.
用"""
之后可以支持多行. (IDE里面可以折叠的.)
这个Event
类和方法我们都还没有写, 所以暂时报错.
setup
添加依赖, 去package页面看添加什么版本: https://pub.dev/packages/built_value
在pubspec.yaml
中添加:
dependencies:
flutter:
sdk: flutter
# other dependencies here
built_value: ^7.0.9
built_collection: ^4.3.2
dev_dependencies:
flutter_test:
sdk: flutter
# other dev_dependencies here
build_runner: ^1.8.0
built_value_generator: ^7.0.9
然后点Packages get
.
Live Templates
这个是IntelliJ系IDE(包括Android Studio)的快捷设置, 目的是为了减少手动输入. (可选.)
打开Preferences
, 搜Live Templates
.
在Dart
的部分点+号新增一个Live Template.
下面Abbreviation选一个适当的缩写, 比如built
.
Template text贴入这段:
abstract class $CLASS_NAME$ implements Built<$CLASS_NAME$, $CLASS_NAME$Builder> {
$CLASS_NAME$._();
factory $CLASS_NAME$([void Function($CLASS_NAME$Builder) updates]) = _$$$CLASS_NAME$;
}
Applicable in Dart选: top-level.
建好之后以后就直接用啦.
建立models抽象类
输入刚才建立的live template的关键字built
, 就会出现要生成的代码, 其中写好自己的类名.
比如我们要建立的model类型是Event
类.
新建event.dart
文件.
在其中输入built
按确认之后, 输入类名Event
, 就建好了:
abstract class Event implements Built<Event, EventBuilder> {
Event._();
factory Event([void Function(EventBuilder) updates]) = _$Event;
}
包括一个私有构造和一个工厂方法. 此时会有一些红色的报错.
这里import 'package:built_value/built_value.dart'
消除Built
类的报错.
根据观察API: https://api.github.com/events 返回的json, 发现还应该有Actor
, Repo
, Payload
三个类.
也都按这个方法建立好.
然后在其中添加字段, 现在看起来是这样了:
import 'package:built_value/built_value.dart';
// imports for models
part 'event.g.dart';
abstract class Event implements Built<Event, EventBuilder> {
String get id;
String get type;
Actor get actor;
Repo get repo;
Payload get payload;
bool get public;
String get createdAt;
Event._();
factory Event([void Function(EventBuilder) updates]) = _$Event;
}
很重要的一步, 就是在类前面添加上一句: part 'event.g.dart';
.
g.dart
是一个惯例, 表明这个文件是生成的代码. part
表示目前这个文件是另一个文件的一部分.
按照同样的方法把几个类都建好.
注意如果有列表字段, 要声明为BuiltList
类型.
运行生成命令
生成命令:
flutter packages pub run build_runner build
需要持续构建和可以用:
flutter packages pub run build_runner watch
这样就不用每次改完代码都需要跑一次命令了.
我们这里用watch, 因为还没有改完.
运行完成之后, 可以看到.g.dart
的文件们都生成了, 报错也消失了.
写Serializers
新建文件serializers.dart
.
import 'package:built_value/serializer.dart';
import 'package:built_value/standard_json_plugin.dart';
// imports for models
part 'serializers.g.dart';
@SerializersFor(const [
Event,
Actor,
Repo,
Payload,
])
final Serializers serializers =
(_$serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();
@SerializersFor
里面列出想要序列化的类.
注意这里要加上StandardJsonPlugin
, 因为built value的json格式不是标准的, 而是所有字段逗号分隔的.
用了StandardJsonPlugin
之后就转换成了标准的JSON格式.
因为我们跑命令的时候用的是watch
, 所以保存修改后serializers.g.dart
文件此时自动生成了.
添加model序列化和反序列化代码
在Event类中添加:
static Serializer<Event> get serializer => _$eventSerializer;
String toJson() {
return json.encode(serializers.serializeWith(Event.serializer, this));
}
static Event fromJson(String jsonString) {
return serializers.deserializeWith(
Event.serializer, json.decode(jsonString));
}
import中除了model类还有:
import 'dart:convert';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import 'serializers.dart';
此时其他几个model类也要添加serializer
, 比如Actor
类中添加:
static Serializer<Actor> get serializer => _$actorSerializer;
重新build生成代码, 报错消失.
现在可以运行测试:
test("parse event", () {
const jsonString = """replace with event json string""";
expect(Event.fromJson(jsonString).id, "11732036753");
});
来检验单个的Event model建立.
可能会遇到的失败情况:
- 一些字段需要被标记为可为空
@nullable
. - 一些字段名和key不匹配, 用
@BuiltValueField
的wireName
标记.
详见后面的Troubleshooting
部分.
如何反序列化顶层列表?
Event的API返回的是一个Event的数组: []
. 这种怎么做呢?
这里有个issue就是关于这个问题, 里面的解决办法挺好: https://github.com/google/built_value.dart/issues/565
在serializers.dart
中添加方法:
T deserialize<T>(dynamic value) =>
serializers.deserializeWith<T>(serializers.serializerForType(T), value);
BuiltList<T> deserializeListOf<T>(dynamic value) => BuiltList.from(
value.map((value) => deserialize<T>(value)).toList(growable: false));
其中BuiltList
需要import 'package:built_collection/built_collection.dart';
.
反序列化Event
数组的方法:
static List<Event> fromEventsListJson(String jsonString) {
final BuiltList<Event> listOfEvents =
deserializeListOf<Event>(json.decode(jsonString));
return listOfEvents.toList();
}
到这一步, 跑我们开头写的两个测试应该都绿了. 如果没绿见Troubleshooting
部分.
泛型的fromJson方法.
上面给serializers
中添加了两个方法. 其中第一个方法是一个泛型的fromJson
方法.
我们测试中的:
expect(Event.fromJson(jsonString).id, "11732036753");
也可以这样写:
expect(deserialize<Event>(json.decode(jsonString)).id, "11732036753");
这样不用给每一个类都写一个fromJson
方法了.
Troubleshooting
可能会有的报错, 问题原因和解决方式.
报错1: failed due to: Invalid argument(s): Unknown type on deserialization. Need either specifiedType or discriminator field.
比如这个样子:
Deserializing '[id, 11732036753, type, PushEvent, actor, {id: 54496419, login: supershell201...' to 'Event' failed due to: Invalid argument(s): Unknown type on deserialization. Need either specifiedType or discriminator field.
这是因为Event
中依赖的类(Actor
, Repo
, Payload
)没有添加serializer.
比如Actor
中:
static Serializer<Actor> get serializer => _$actorSerializer;
添加上重新build生成代码即可.
报错2: Tried to construct class "XXX" with null field
比如:
Deserializing '[id, 11732036753, type, PushEvent, actor, {id: 54496419, login: supershell201...' to 'Event' failed due to: Deserializing '[id, 54496419, login, supershell2019, display_login, supershell2019, gravatar...' to 'Actor' failed due to: Tried to construct class "Actor" with null field "displayLogin". This is forbidden; to allow it, mark "displayLogin" with @nullable.
此时, 先不要着急把字段标记为@nullable
.
而是要看这个字段是否真的为null, 很有可能是因为字段名称和json中的key不匹配造成的, 比如json中是个蛇形命名.
查看了一下果然就是, 解决办法:
@BuiltValueField(wireName: 'display_login')
String get displayLogin;
如果字段真的是有可能为null的情况, 那么加上@nullable
:
比如:
@BuiltValueField(wireName: 'ref_type')
@nullable
String get refType;
报错3: FormatException: Control character in string
比如:
FormatException: Control character in string (at line 25, character 129)
... replica::on_client_write(dsn::message_ex *request, bool ignore_throttling)
相关issue: https://github.com/dart-lang/convert/issues/10
解决的办法就是在测试的字符串声明前加一个r
:
const jsonString = r"""replace with events list json string""";
Model生成工具推荐
有个很棒的工具: https://charafau.github.io/json2builtvalue/
左边输入json字符串, 写好命名, 点击之后右边就会出现那些本来需要手动写的代码.
生成的Event
类是这样:
library event;
import 'dart:convert';
import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
part 'event.g.dart';
abstract class Event implements Built<Event, EventBuilder> {
Event._();
factory Event([updates(EventBuilder b)]) = _$Event;
@BuiltValueField(wireName: 'id')
String get id;
@BuiltValueField(wireName: 'type')
String get type;
@BuiltValueField(wireName: 'actor')
Actor get actor;
@BuiltValueField(wireName: 'repo')
Repo get repo;
@BuiltValueField(wireName: 'payload')
Payload get payload;
@BuiltValueField(wireName: 'public')
bool get public;
@BuiltValueField(wireName: 'created_at')
String get createdAt;
String toJson() {
return json.encode(serializers.serializeWith(Event.serializer, this));
}
static Event fromJson(String jsonString) {
return serializers.deserializeWith(
Event.serializer, json.decode(jsonString));
}
static Serializer<Event> get serializer => _$eventSerializer;
}
哈哈, 看到这里是不是有种被骗了的感觉.
有了这个很棒的工具之后根本不用自己很小心地写一个一个model类了, 只需要写一个serializers.dart
文件:
part 'serializers.g.dart';
@SerializersFor(const [
Event,
Actor,
Repo,
Payload,
])
final Serializers serializers =
(_$serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();
T deserialize<T>(dynamic value) =>
serializers.deserializeWith<T>(serializers.serializerForType(T), value);
BuiltList<T> deserializeListOf<T>(dynamic value) => BuiltList.from(
value.map((value) => deserialize<T>(value)).toList(growable: false));
然后把要反序列化的类加进来, 再跑命令行生成代码, 就可以了.
经历一下前面的手动过程可能理解得更好一些, 也知道各种问题的原因.
以后使用直接用工具就方便多了.
参考资料
- 官方文档: https://flutter.dev/docs/development/data-and-backend/json
- Flutter实战11.7: https://book.flutterchina.club/chapter11/json_model.html
- built value github
- 生成工具: https://charafau.github.io/json2builtvalue/
Flutter json 2 model with Built Value的更多相关文章
- 【疯狂造轮子-iOS】JSON转Model系列之二
[疯狂造轮子-iOS]JSON转Model系列之二 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇<[疯狂造轮子-iOS]JSON转Model系列之一> ...
- 【疯狂造轮子-iOS】JSON转Model系列之一
[疯狂造轮子-iOS]JSON转Model系列之一 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 之前一直看别人的源码,虽然对自己提升比较大,但毕竟不是自己写的,很容易遗 ...
- Swift实现JSON转Model - HandyJSON使用讲解
背景: 很多时候,我们从服务端请求下的数据都是Json格式,我们需要拿这些数据显示到我们的UI界面. 因此,我们的做法基本都会先将json转为方便使用的数据模型,或者也可以直接转字典解决. 在OC中, ...
- Codable实现json转Model,是时候干掉HandyJSON了!
自从开始使用Swift做项目,一直都在使用HandyJSON,不可否认,HandyJSON在Swift4.0是个好东西,也尝试过其它json转mode的工具,最终发现还是HandyJSON最好用. 去 ...
- C# json转model 以及model转json
1.json转model TestModel tm = new TestModel(); JavaScriptSerializer js = new JavaScriptSerializer();tm ...
- [译]Flutter JSON和序列化
[译]Flutter JSON和序列化 很难想象一个移动应用程序不需要与Web服务器通信或在某些时候容易存储结构化数据.制作网络连接的应用程序时,迟早需要消耗一些好的旧JSON. 本指南介绍了如何 ...
- flutter json转字符串 字符串转json
一段json字符串 var jsonStr = '{\"errorCode\": \"0\", \"message\": \"成功 ...
- Flutter Json序列号和反序列化遇到问题 Missing "part 'xxx.g.dart';"
/** * * 1.@JsonSerializable() 这是表示告诉编译器这个类是需要生成Model类的 * 2,@JsonKey 由于服务器返回的部分数据名称在Dart语言中是不被允许的, * ...
- Flutter JSON解析与复杂模型转换技巧及实例
其实转换成model类是有好处的,转换后可以减少上线后APP崩溃和出现异常,所以我们从这节课开始,要制作model类模型,然后用model的形式编辑UI界面. 类别json的分析 比如现在从后台得到了 ...
随机推荐
- Iterator迭代器解决[为何禁止在foreach内增删]
迭代器的应用场景: 1.对集合进行增加删除,禁止使用foreach,循环的动态操作2.倒序遍历3.遍历循环 步入正题:为何禁止在foreach内进行增删? 先看一下代码: /** * ...
- Spring-增强方式注解实现方式
SpringAOP增强是什么,不知道的到上一章去找,这里直接上注解实现的代码(不是纯注解,纯注解后续会有) 创建业务类代码 @Service("dosome")//与配置文件中&l ...
- C语言数据类型char
char简介 char是C/C++中的基本数据类型,可容纳单个字符的一种基本数据类型. char占一个字节,也就是8个bit(二进制位),0000 0000 ~ 1111 1111,在无符号类型时ch ...
- python2下经典爬虫(第一卷)
python2.7的爬虫个人认为比较经典在此我将会用书中的网站http://example.webscraping.com作为案例 爬虫第一步:进行背景调研 了解网站的结构资源在网站的robots.t ...
- ffmpeg android移植
CMake语法简介(androidstudio中利用CMake开发NDK): http://blog.csdn.net/u013718120/article/details/62883711FFmpe ...
- [洛谷P3391] 文艺平衡树 (Splay模板)
初识splay 学splay有一段时间了,一直没写...... 本题是splay模板题,维护一个1~n的序列,支持区间翻转(比如1 2 3 4 5 6变成1 2 3 6 5 4),最后输出结果序列. ...
- Java Servlet XML文件配置
- 吴裕雄--天生自然KITTEN编程:青蛙答题过河
- spring入门(14)
AOP是一个新的专题,基础部分主要是入门 后续的五.六.七都属于AOP专题: 所以有必要对这三章要学什么有个全局的认识. 1 概要 1 什么是AOP及实现方式 介绍了AOP的用途,以及大致的实现方案 ...
- 《软件自动化测试开发-Java和Python测试开发指南》第6次印刷
2017年1月 第1次印刷 2017年5月 第2次印刷 2017年9月 第3次印刷 2017年11月 第4次印刷 2018年4月 第5次印刷 2018年6月 第6次印刷 欢迎留言,点赞前2名,可获2折 ...