这几天我们来介绍一些工具,在之後实作专案时会很常用到
我们的app 常常需要一些来自後台的资料,像是与Web 服务器进行通信来传递资讯,而这些数据传输方式基本都是用JSON
,JSON
是我们开发中最常使用的一种资料格式,官方文件
我们通常会将需要发送的数据序列化为JSON格式的字串流结构化资料进行传输,而反序列化则是将从获得的JSON格式字串流结构化资料进行反序列化,重建我们所要的资料结构数据
手动序列化数据
规模较小的专案可以使用手动序列化
使用Flutter 内建的dart:convert
的库,这个library 包含了一个简单的JSON编码器和解码器
例如:
import 'dart:convert';
void main() {
String jsonString = ''' {
"name": "John Smith",
"email": "[email protected]"
} ''';
manualDecode(jsonString);
}
void manualDecode(String jsonString) {
Map<String, dynamic> user = jsonDecode(jsonString);
print('Howdy, ${user['name']}!'); //印出 Howdy, John Smith!
print('We sent the verification link to ${user['email']}.'); //印出 We sent the verification link to [email protected].
}
然而,jsonDecode()
返回一个Map<String, dynamic>
,也就是说直到执行时我们才知道值的类型,代表使用这个方法,我们就失去了大部分的静态类型语言特性:类型安全,自动补全以及最重要的编译时异常,例如,当我们要存取name
栏位,但是名称却打错了,此时编译器在编译时不会帮忙报错
解决方法:在Model 类别中序列化JSON
通过事先定义与Json结构对应的Model类,然後在请求到数据後再动态根据数据创建出Model类的实例
例如:
建立一个与上述范例Json 对应的Model 类别,User:
User.fromJson
建构函数,用於从map 结构资料中构造出一个新的User
实例toJson
方法,将User
实例转化为一个map 结构化资料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,
};
}
通过这种方法可以拥有类型安全, name
和email
字段的自动补全字段以及编译时异常(检测),如果你发生了笔误或者把String
类型的字段看成了int
类型,app在编译时就不会通过,而不是在执行时抛出异常
解码序列化逻辑现在移动到了模型内部,通过此方法可以很容易地解码/反序列化一个 user
import 'dart:convert';
void main() {
String jsonString = ''' {
"name": "John Smith",
"email": "[email protected]"
} ''';
Map userMap = json.decode(jsonString);
var user = new User.fromJson(userMap);
print('Howdy, ${user.name}!');
print('We sent the verification link to ${user.email}.');
}
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,将User
实例传到jsonEncode()
函数中,你不需要调用toJson()
方法,因为jsonEncode()
已经帮你做了这件事
String json = jsonEncode(user);
通过这种方法,被调用的代码根本不需要担心序列化JSON数据的问题,然而,模型Model 类别仍然是必须的。在一个生产环境下的App,你可能希望确保序列化数据能正确奏效。所以User.fromJson()
和 User.toJson()
方法都需要单元测试以便验证正确的行为
然而,现实场景通常不是那麽简单,有时候响应的JSON API 会更加复杂,例如它可能会包含一些相邻的JSON 对象,而这些对象同样需要使用它的model 类进行解析,此时我们就需要使用代码来自动生成库序列化JSON 数据
利用代码自动生成序列化数据
尽管有其它的library 可以使用,我们来介绍官方推荐的函式库json_serializable,由於序列化数据不再需要手动编写或者维护,你可以将序列化JSON 数据在运行时的异常风险降到最低
添加依赖:
...
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.0
json_annotation: ^3.1.0 #当前版本
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^1.10.3 #当前版本
json_serializable: ^3.5.0 #当前版本
...
以 json_serializable 的方式创建model类
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,那麽我们可以使用@JsonKey标注:
/// Tell json_serializable that "registration_date_millis" should be
/// mapped to this property.
@JsonKey(name: 'registration_date_millis')
final int registrationDateMillis;
当你首次创建json_serializable
类时,你会得到类似下图的错误
这些错误是完全正常的,这是因为Model类的生成代码还不存在。为了解决这个问题,我们必须运行代码生成器来为我们生成序列化模板。有两种运行代码生成器的方法:
透过在专案的根目录下执行flutter packages pub run build_runner build
,这触发了一次性构建,我们可以在需要时为我们的Model生成json序列化代码,它通过我们的源文件,找出需要生成Model类的源文件(包含@JsonSerializable标注的)来生成对应的.g.dart
文件。一个好的建议是将所有Model 类放在一个单独的目录下,然後在该目录下执行命令
虽然这非常方便,但如果我们不需要每次在Model类中进行更改时都要手动运行构建命令的话会更好
使用watcher可以使我们的源代码生成的过程更加方便。它会监视我们项目中文件的变化,并在需要时自动构建必要的文件,我们可以通过flutter packages pub run build_runner watch
在项目根目录下运行来启动watcher。只需启动一次观察器,然後它就会在後台运行,这是安全的
使用json_serializable
,在User
类中你可以忘记所有手动序列化的JSON数据。源代码生成器会创建一个名为user.g.dart
的文件,它包含了所有必须的序列化数据逻辑。你不必再编写自动化测试来确保序列化数据奏效。现在由库来负责确保序列化数据能正确地奏效
你可能在代码中用了嵌套类,在你把类别作为参数传递给一些服务(比如Firebase)的时候,你可能会遇到Invalid argument
错误
比如下面的这个Address
类:
import 'package:json_annotation/json_annotation.dart';
part 'address.g.dart';
@JsonSerializable()
class Address {
String street;
String city;
Address(this.street, this.city);
factory Address.fromJson(Map<String, dynamic> json) => _$AddressFromJson(json);
Map<String, dynamic> toJson() => _$AddressToJson(this);
}
一个Address
类被嵌套在User
类中使用:
import 'address.dart';
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable()
class User {
String firstName;
Address address;
User(this.firstName, this.address);
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
在终端机中运行flutter pub run build_runner build
创建* .g.dart
文件,但私有函数如_ $ UserToJson()
会看起来像下面这样
user.g.dart
:
(
Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
'firstName': instance.firstName,
'address': instance.address,
};
看起来没有什麽问题,但如果print
User 实例时:
import 'package:json_tutorial/user.dart';
import 'address.dart';
void main() {
Address address = Address("My st.", "New York");
User user = User("John", address);
print(user.toJson()); // 印出 {firstName: John, address: Instance of 'Address'}
//不是我们要的结果:{name: John, address: {street: My st., city: New York}}
}
为了得到正常的输出,你需要在类别声明之前为@JsonSerializable
方法加入explicitToJson: true
参数
user.dart
:
import 'address.dart';
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable(explicitToJson: true)
class User {
String firstName;
Address address;
User(this.firstName, this.address);
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
main.dart
:
import 'package:json_tutorial/user.dart';
import 'address.dart';
void main() {
Address address = Address("My st.", "New York");
User user = User("John", address);
print(user.toJson()); // 印出 {firstName: John, address: {street: My st., city: New York}}
}
接下来的这几天,会疯狂运用到上个单元教的阵列,也会碰触一些演算法的概念,而今天要来介绍的是二元搜寻法...
YOLO 是一个不断改进和优化的物件侦测系列,除了前三个版本,在 2020 年时,YOLOv4 也问...
本篇同步发布於个人Blog: [PoEAA] Domain Logic Pattern - Tabl...
在写React的时候其实有分为两种写法 Class Component this.state or ...
今天要和来大家说明一下分析的基本框架要如何展开。这边提供的是一套思考的流程,提醒大家展开分析的过程中...