亲宝软件园·资讯

展开

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类中,我们可以发现:

使用这种方法,调用代码时可以具有类型安全及编译时异常提醒。如果我们输入了错别字,或者将字段视为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中的nameemail字段进行编码和解码的代码。

如果需要的话,我们还可以定制命名策略,比如,如果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:这里所说的解码和编码,对应的是DecodeEncode

加载全部内容

相关教程
猜你喜欢
用户评论