flutter中的JSON和序列化方法及使用详解
前端那些年 人气:0引言
很难想象一款移动应用程序不需要与web服务器通信,也不需要存储结构化数据。在开发一款网络连接的应用程序时,它迟早会需要使用一些JSON。
这里简单介绍一下JSON在flutter中的使用。
Tips
:
编码和序列化是将数据结构转换为字符串的同一件事。解码和反序列化是将字符串转换为数据结构的相反过程。然而,序列化通常也指将数据结构转换为更易于阅读的格式的整个过程。
哪种JSON序列化方法适合
这里主要简单介绍两种序列化方式:
- 手动序列化
- 使用代码自动序列化
不同的项目复杂度以及用例都不同,对于一些较小的项目或者类似原型的的应用,使用代码生成可能有些大材小用,而对于有很多不同json模型的应用程序,使用手动序列化则除了无聊之外,有可能会产生不必要的问题和麻烦。
手动进行序列化
手动进行json解码说的是使用dart:convert
内置的json解码器,通过将原始的json数据传递给jsonDecode()
方法,然后在返回的Map<String, dynamic>
这个类型的数据中我们可以找到我们想用的数据。不需要别的依赖和其他的设置过程,对于验证一些快速的原型或者小型的项目非常有效。
当项目逐渐变的越来越大的时候,手动解码可能会表现的不尽人意。手动编写解码逻辑可能会变得越来越难以管理,而且变得非常容易出错,如果访问到不存在的字段,或者编写时有拼写错误,代码在运行时就会发生错误。
使用代码自动序列化
对于中大型项目来说,使用代码自动进行序列化可能会是一个比较不错的选择,意味着我们可以使用外部的依赖库来生成我们想要的模版。我们通过设置一些初始化的配置,然后运行一个file watcher
从我们的模型类中生成我们想要的代码数据。
比如我们可以使用:json_serializable
或者build_value
诸如之类的库。
这种方法适用于更大的项目。不需要手工编写模版,并且在编译时会捕捉到访问JSON字段时的拼写错误。
代码生成的缺点是需要一些初始设置。另外,生成的源文件可能会在项目导航器中产生视觉上的混乱。
Flutter 中是否有 GSON/Jackson/Moshi 之类的序列化类库?
GSON
以及Jackson
都是 Java中用来序列化json的类库。
Moshi
则是Kotlin
中用来序列化json的类库。
事实上Flutter
中并没有类似的库。
因为,这样的库需要使用运行时反射,这在Flutter中是禁用的。运行时反射会干扰【树抖动】treeShaking
,Dart已经支持了很长时间。通过treeShaking
树抖动,您可以从发布版本中“抖掉”未使用的代码,这可以优化应用程序的大小。
由于反射默认情况下会隐式使用所有代码,因此很难进行treeShaking
树抖动。这些工具无法知道哪些部分在运行时未使用,因此冗余代码很难去除。使用反射时,无法轻松优化应用程序大小。
虽然我们不能在Flutter中使用运行时反射,但有些库提供了类似的API,是基于代码生成。
使用dart:convert内置库手动进行序列化
Flutter中的基本JSON序列化非常简单。Flutter有一个内置的dart:convert
库,其中包含一个简单的JSON编码器和解码器。
看下面的示例:
{ "name": "John Smith", "email": "john@example.com" }
使用dart:convert
库,我们有两种方法进行序列化。
调用jsonDecode()方法:
Map<String, dynamic> user = jsonDecode(jsonString); print('Howdy, ${user['name']}!'); print('We sent the verification link to ${user['email']}.');
但是需要注意的是,jsonDecode()
方法会返回一个类型为Map<String, dynamic>
的类型,这样的话,我们就特别需要注意json中字段的各种类型。
在模型类中序列化JSON
此外,我们可以引入一个简单的模型类(在本例中称为User)来解决前面提到的问题。在User类中,我们可以发现:
User.fromJson()
构造函数,用于从Map构造新的User实例。toJson()
方法,将User实例转换为Map。
使用这种方法,调用代码时可以具有类型安全及编译时异常提醒。如果我们输入了错别字,或者将字段视为int而不是String,应用程序将不会编译,而不会在运行时崩溃。
// user.dart class User { final String name; final String email; User(this.name, this.email); User.fromJson(Map<String, dynamic> json) : name = json['name'], email = json['email']; Map<String, dynamic> toJson() => { 'name': name, 'email': email, }; }
解码逻辑的责任现在转移到模型本身内部。使用这种新方法,您可以轻松地解码User:
Map<String, dynamic> userMap = jsonDecode(jsonString); var user = User.fromJson(userMap); print('Howdy, ${user.name}!'); print('We sent the verification link to ${user.email}.');
和解码(Decode)相反的是编码(Encode),如果我们想要对User进行编码,我们可以使用jsonEncode()
方法:
String json = jsonEncode(user);
使用这种方法,调用代码根本不必担心JSON序列化。然而,模型类仍然必须这样做。在生产应用程序中,我们需要确保序列化工作正常进行。在实际开发过程中,User.fromJson()
和User.toJson()
方法可能都需要进行单元测试以保证结果的正确性。
使用序列化库
尽管有其他库可用,但是这里使用了json_serializable
,这是一个自动源代码生成器,可为我们生成json序列化模版。
要在项目中包含json_serializable
,需要一个常规依赖项和两个开发依赖项。简而言之,开发依赖项是不包含在我们的应用程序源代码中的依赖项,它们只在开发环境中使用。
我们需要在pubspec.yaml进行如下配置:
**pubspec.yaml** dependencies: # Your other regular dependencies here json_annotation: <latest_version> dev_dependencies: # Your other dev_dependencies here build_runner: <latest_version> json_serializable: <latest_version>
然后在项目根文件夹中运行flutter pub-get
以安装依赖。
然后我们以json_serializable
的方式创建模型类:
// user.dart import 'package:json_annotation/json_annotation.dart'; /// This allows the `User` class to access private members in /// the generated file. The value for this is *.g.dart, where /// the star denotes the source file name. part 'user.g.dart'; /// An annotation for the code generator to know that this class needs the /// JSON serialization logic to be generated. @JsonSerializable() class User { User(this.name, this.email); String name; String email; /// A necessary factory constructor for creating a new User instance /// from a map. Pass the map to the generated `_$UserFromJson()` constructor. /// The constructor is named after the source class, in this case, User. factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json); /// `toJson` is the convention for a class to declare support for serialization /// to JSON. The implementation simply calls the private, generated /// helper method `_$UserToJson`. Map<String, dynamic> toJson() => _$UserToJson(this); }
通过这种设置,源代码生成器生成用于对JSON中的name
和email
字段进行编码和解码的代码。
如果需要的话,我们还可以定制命名策略,比如,如果API返回带有的对象带有snake_case
属性,并且我们希望在模型中使用lowerCamelCase,则可以使用带有name参数的@JsonKey注释:
/// Tell json_serializable that "registration_date_millis" should be /// mapped to this property. @JsonKey(name: 'registration_date_millis') final int registrationDateMillis;
服务器和客户端最好都遵循相同的命名策略。
@JsonSerializable()
提供了fieldRename
的枚举,用于将dart字段完全转换为JSON键。
修改@JsonSerializable(fieldRename:fieldRename.sake)
相当于向每个字段添加@JsonKey(name:“<snake_case>”)
。
服务器返回的数据是不确定的,所以有必要验证和保护客户端上的数据。
其他常用的@JsonKey注释包括:
/// Tell json_serializable to use "defaultValue" if the JSON doesn't /// contain this key or if the value is `null`. @JsonKey(defaultValue: false) final bool isAdult; /// When `true` tell json_serializable that JSON must contain the key, /// If the key doesn't exist, an exception is thrown. @JsonKey(required: true) final String id; /// When `true` tell json_serializable that generated code should /// ignore this field completely. @JsonKey(ignore: true) final String verificationCode;
运行代码生成实用程序
当第一次创建json_serializable
类时,会出现类似下图所示的错误。
这些错误完全是正常的,只是因为为模型类生成的代码还不存在。要解决此问题,我们需要运行生成序列化样板的代码生成器。
运行代码生成器有两种方法。
- 一次性代码生成
- 持续生成代码
一次性代码生成
通过在项目根目录中运行
flutter pub run build_runner build --delete-conflicting-outputs
我们可以在需要时为模型生成JSON序列化代码。这将触发一次性构建,该构建将遍历源文件,选择相关文件,并为它们生成必要的序列化代码。
虽然这很方便,但如果我们不必每次在模型类中进行更改时都手动运行构建,那就更好了。
持续生成代码
观察者模式使我们的源代码生成过程更加方便。它监听项目文件中的更改,并在需要时自动生成必要的文件。 通过在项目根目录中运行
flutter pub run build_runner watch --delete-conflicting-outputs
可以安全地启动一次观察程序,并让它在一直后台运行。
使用json_serializable模型
要以JSON_serializable
的方式解码JSON字符串,实际上不需要对我们之前的代码进行任何更改。
Map<String, dynamic> userMap = jsonDecode(jsonString); var user = User.fromJson(userMap);
编码也是如此。调用API与之前相同。
String json = jsonEncode(user);
使用json_serializable
,我们可以放弃User类中的任何手动json序列化。源代码生成器创建一个名为user.g.dart的文件,该文件具有所有必要的序列化逻辑。我们不再需要编写自动化测试来确保序列化工作,现在库负责确保序列化工作正常。
ps:这里所说的解码和编码,对应的是Decode
和Encode
。
加载全部内容