如果你已经入门了Flutter的开发,并准备实现一个包含网络数据请求并解析数据显示在界面上的应用,那么你一定需要了解在Flutter中应该如何完成对JSON数据的解析和处理。
如果还没有看过官方的相关文档,请移步至JSON和序列化查看。
可以看到Flutter使用的sdk中提供了dart:convert 包用于JSON的序列化和反序列化,其功能与iOS的NSJSONSerialization类似,都是直接将JSON数据转成NSDictionary / NSArray(或者JAVA中的Map/List), 然后开发时通过输入字段名字符串的方式从中取值,非常的不方便。
另一种方式则是编写模型类,这样可以在已知JSON结构和字段的情况下预先设置好类结构,开发时则可以方便地通过’点语法’进行数据字段取值,获得IDE代码提示的辅助,从而减少手误提高开发效率。然而编写大量相似的模板类代码无聊又费力,即使使用官方提供的json_serializable package包进行代码生成也还是相当麻烦。
如果开发过android、iOS或者java后台应用,那么你应该曾使用过各种插件用于将JSON字符串解析生成模板类(例如GsonFormat和ESJsonFormat ),这些工具极大提高了开发效率。
那么Flutter开发中有没有类似的工具可以使用呢?本文就将介绍我参照这些工具专为Flutter编写的一个小工具(还有一个类似的在线工具:JSON to Dart,感兴趣的可以试试~~):
JSONFormat4Flutter
github地址:github.com/debuggerx01…
使用操作演示:
使用说明
1.界面操作 (参考录屏:parse.gif)
- 工具运行以后,先将复制好的json字符串粘贴到左侧文本框,然后点击’格式化’按钮;如果提示出错请检查json是否合法
- 格式化成功后左侧json将会按照缩进格式化显示,并且右侧表格将显示分析得出的json结构,’Fields’列显示层级和原始分析数据,’Name’列显示每个字段的名称,’Type’列用于设定字段的数据类型
- 对于普通数据类型(int、 double、 boolean、 String),Types列的类型将会自动给出,请尽量避免在上面滚动鼠标滚轮导致类型选择改变
- 对于值null的字段,Types列的类型会自动设置为Object,并以黄色背景作为警告。此时如果直接生成代码也是可以使用的,只是该字段在使用时可能需要手动强转,所以建议在知晓该字段实际类型情况下尽量补全json字符串后再点击’格式化’,或者在类型下拉框中指定实际的基本数据类型
- 对于自定义对象类型(或者说字典/Map),’Fields’的对应输入框将留空并设为红色背景,需要您手动输入类型名称,并请注意:
- 任意一个字段没有输入类型名时点击代码生成按钮,都将弹出警告提示并拒绝生成代码
- 设置类型名时可以参考同一行’Name’栏的值进行设置以方便使用时识别字段,一般情况下推荐直接将’Name’栏内容首字母大写作为类型名
- 但是需要注意,类型名不可与’Name’栏内容完全相同,且不能是dart中的关键字,否则生成的代码将包含语法错误
- 一般情况下第一行的数据类型为对象且’Name’栏内容为空,设置第一列的’Types’即为生成的bean的顶级对象类名,推荐使用’该json的作用+Resp/Bean’形式进行命名以方便管理
- 对于数组类型,’Types’栏将被自动设置,并且:
- 数组的泛型类型取决于数组的内容的类型,也就是下一行设置的类型;当数组下一行的内容类型变化时泛型也会自动改变
- 支持数组的嵌套泛型传递
- 支持空数组,并且生成的代码中其泛型会被设置为dynamic
- 特殊的,如果json本身的顶层级不是对象而是数组,那么需要为第一行的’Name’栏设置类型名称,获取顶层级数组数据的方式为对象bean.list
- 确认设置无误后,点击’生成Bean’按钮,左侧json显示栏的内容将被替换为生成的代码,可以使用鼠标键盘全选复制,或者直接点击下方的’复制’按钮,然后将代码粘贴到IDE中,完成解析流程
2.生成代码说明 (参考录屏:use.gif)
-
反序列化(json字符串->对象)
将生成的代码粘贴到dart源文件中后,即可以在任意地方导包使用,一般方法为(以http.get请求为例):var response = await HTTP.get(url); var resp = BeanResp(response.body);
也就是说,将请求到的json内容作为参数传递给BeanResp的默认构造函数,这样生成的resp对象即是请求到内容的实体。 需要说明的是,默认构造既可以传入json的原始字符串,也可以传入已经用原生json.decode()方法解析过的json对象(这主要是为了照顾使用dio库进行数据请求时结果数据会被自动解析成json对象的情况)。 只有顶级对象拥有默认构造方法,而其他子层级对象将使用xxx.fromJson()的命名构造进行对象创建。
-
序列化(对象->json字符串)
与官方样例的处理方式不同,直接调用对象的toString()方法即可得到json字符串完成序列化操作 -
手动创建对象
为了方便大部分使用场景下的便利性,bean的默认构造函数被用来实现反序列化,所以如果想要在代码中手动传参创建bean对象,可以使用xxx.fromParams()命名构造来完成。
简易运行方式:
在 Release 页面中,选择下载对应平台最新的二进制文件后——
linux:
在程序目录打开终端后执行:chmod u+x Formatter_linux && ./Formatter_linux
mac:
在程序目录打开终端后执行:chmod u+x Formatter_mac && ./Formatter_mac
windows:
直接双击运行 Formatter_win.exe
源码运行(以MAC为例)
没有python运行环境的用户需要先安装python
mac中可以使用如下命令安装
brew install python3
brew install pip3
pip3是python3的包管理工具
brew 可以参考下面的链接
brew.sh/index_zh-cn
运行库的时候会可能会提示
Traceback (most recent call last):
File "formater.py", line 8, in <module>
from mainwindow import *
File "/Users/cjl/IdeaProjects/flutter/sxw-flutter-app/JSONFormat4Flutter/mainwindow.py", line 9, in <module>
from PyQt5 import QtCore, QtGui, QtWidgets
ModuleNotFoundError: No module named 'PyQt5'
这时候可以直接用 pip3 install PyQt5
pip3 install pyperclip
等待安装完成
(注:brew安装最新版python3可能会出现ssl模块丢失导致pip3无法正常使用,此时也可以考虑直接在python官网下载pkg包方式安装python)
后面使用就是在命令行敲入 python3 formatter.py
一些问题的说明
如果有什么问题,请在github上与我联系。下面列出几个已有的问题:
- 为什么不做成AS/idea插件,而选择PyQt编写工具
原因其一是我曾写过简单的AS/idea插件,发现开发资料相当匮乏,调试也很麻烦,虽然也有一些现成的插件源码可供参考,但是开发这样一个工具的周期预计会大幅超过我当时允许的时间(本工具第一个可用版本的实际开发时间为3天多一点);
其二是由于Flutter既可以用AS/idea开发也可以用VSCode开发的特性(而且据我观察两者使用人数比例相仿),两种IDE体系下开发插件的语言工具及流程都完全不同,一旦我选择了开发原生IDE插件,那么势必需要编写维护两份代码,成本太高;
其三是因为python强大的文本处理能力使得它特别适合这种模板生成的工作,而且正巧之前的工作中就有利用python解析excel数据生成其他语言源代码的脚本,可以快速借用已有的代码逻辑来实现功能;
其四是得益于PyQt强大的跨平台能力,可以方便快捷地打出三个桌面平台的应用包,从而达到接近原生插件的通用性(期待Flutter的桌面版项目早日成熟,这样以后开发桌面工具也多一种选择)
另外,可以参考issue #9中的演示,将工具添加到IDE的外部工具中以方便打开,这样使用体验就与IDE插件更加相似了 - 生成的代码与官网上的样例风格不符
因为我写这个玩意的时候官网文档还没有现在的样例模板(也可能单纯是我没注意到),所以完全是自己构思的方式来做的。后来看到样例后也想过仿照那个格式改一下,但转念一想其实也没什么本质区别,加上又忙就一直没动。我做这个工具主要还是以能够处理各种奇怪json为目标的,比如为了处理多层list嵌套,顶级结构为list,list内部元素为基本数据类型等,这些花了不少精力……而且生成的代码里没用样例里类型转换,所以dart2升级强类型模式以后很多人根据那个样板写的json解析都报错的,而这个工具生成的却一直能用,既然能够保证功能,具体的风格方式问题应该也没必要太过纠结了
- 如何实现通过泛型返回不同的类型
这主要是一些之前做android开发的同学习惯于android开发中流行的’网络请求封装‘,就是假设后台返回的JSON拥有相同的一级结构,比如都是
{"code":0,"msg":"success","data":{}}
这种结构,所有的请求结果都需要先进行code和msg字段的判断处理,然后要使用的实际数据全在data字段中,所以希望有一个统一的处理函数可以进行code和msg字段的处理,并通过指定泛型或class来返回指定类型的data内容对象。
虽然我个人认为这只是个习惯问题,没必要坚持,由于dart语法特性确实不好实现,那么不如换种形式一样能处理地很好,但是因为见到不止一个人有这种疑问,所以我试着用这种思路写了个dart版本的demo仅供参考,希望有人能提供更好更优雅的方案:
Person接口返回的json: {"code":0,"msg":"success","data":{"name":"debuggerx","age":26}}
Phone接口返回json: {"code":0,"msg":"success","data":{"model":"MI6X","price":1999}}
PersonResp.dart:
import 'dart:convert' show json;
class PersonResp {
int code;
String msg;
Person data;
PersonResp.fromParams({this.code, this.msg, this.data});
factory PersonResp(jsonStr) => jsonStr is String ? PersonResp.fromJson(json.decode(jsonStr)) : PersonResp.fromJson(jsonStr);
PersonResp.fromJson(jsonRes) {
code = jsonRes['code'];
msg = jsonRes['msg'];
data = new Person.fromJson(jsonRes['data']);
}
@override
String toString() {
return '{"code": $code,"msg": ${msg != null?'${json.encode(msg)}':'null'},"data": $data}';
}
}
class Person {
int age;
String name;
Person.fromParams({this.age, this.name});
Person.fromJson(jsonRes) {
age = jsonRes['age'];
name = jsonRes['name'];
}
@override
String toString() {
return '{"age": $age,"name": ${name != null?'${json.encode(name)}':'null'}}';
}
}
PhoneResp.dart:
import 'dart:convert' show json;
class PhoneResp {
int code;
String msg;
Phone data;
PhoneResp.fromParams({this.code, this.msg, this.data});
factory PhoneResp(jsonStr) => jsonStr is String ? PhoneResp.fromJson(json.decode(jsonStr)) : PhoneResp.fromJson(jsonStr);
PhoneResp.fromJson(jsonRes) {
code = jsonRes['code'];
msg = jsonRes['msg'];
data = new Phone.fromJson(jsonRes['data']);
}
@override
String toString() {
return '{"code": $code,"msg": ${msg != null?'${json.encode(msg)}':'null'},"data": $data}';
}
}
class Phone {
int price;
String model;
Phone.fromParams({this.price, this.model});
Phone.fromJson(jsonRes) {
price = jsonRes['price'];
model = jsonRes['model'];
}
@override
String toString() {
return '{"price": $price,"model": ${model != null?'${json.encode(model)}':'null'}}';
}
}
只需要编写一个BaseResp.dart如下:
import 'dart:convert' show json;
class BaseResp<T> {
int code;
String msg;
T data;
factory BaseResp(jsonStr, Function buildFun) =>
jsonStr is String ? BaseResp.fromJson(json.decode(jsonStr), buildFun) : BaseResp.fromJson(jsonStr, buildFun);
BaseResp.fromJson(jsonRes, Function buildFun) {
code = jsonRes['code'];
msg = jsonRes['msg'];
/// 这里可以做code和msg的处理逻辑
data = buildFun(jsonRes['data']);
}
}
那么在解析的位置可以写如下代码:
var response = await HTTP.get(url);
Person data = BaseResp<Person>(response.body, (res) => Person.fromJson(res)).data;
///或者
Phone data = BaseResp<Phone>(response.body, (res) => Phone.fromJson(res)).data;
也就是说,通过构造BaseResp对象,指定data对象的泛型,并传入data对象特有的fromJson构造函数,即可实现对象的创建,最后通过’.data’取值,即可得到想要的特定类型的data。
当然,那一长串挺长的,每次写这么一大段也有点麻烦,可以把这一行代码做成代码模板,只留下泛型部分作为模板变量即可。
更新:
有小伙伴指出,上面的BaseResp并不能很好地处理json中data的类型为数组的情况,也就是是类似: {"code":0,"msg":"success","data":[{"name":"debuggerx","age":26},{"name":"dx","age":27}]}
data的类型为List<Person>这种情况。此时可以参考issue 11扩充一个BaseRespList即可解决这个问题。
从 0 到 1:我的 Flutter 技术实践 | 掘金技术征文,征文活动正在进行中
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/9525.html