使用 Flutter 反序列化 JSON 的一些选项

文 /?Andrew Brogdonhtml

 

在某种程度上,大多数应用都须要与外界互动,并从在线终端地址获取数据。借助 Dart 的 http 包,发出 HTTPS get 请求以获取天气预报或世界杯最终比分变得很是简单:git

 

1import 'dart:async'; ? ?github

2import 'package:http/http.dart' as http;?json

3数组

4??final response=await http.get(myEndpointUrl); ? ?架构

5if (response.statusCode==200) { ? ?app

6// use the data in response.body ? ?框架

7} else { ? ?机器学习

8??// handle a failed request ? ?async

9??}?

 

response.body 中的数据多是 JSON 字符串,而咱们还须要完成一些工做,才能将其用于微件。首先,您须要将字符串解析为更易于管理的 JSON 表示形式。而后,您必须将该表示形式转化为模型或其余一些强类型变量,如此一来,您就能够有效地使用这些字符串。

 

幸运的是,Dart 团队和社群对 JSON 已进行过诸多讨论,而且可以提供解决方案。我会按照复杂性从低到高的顺序介绍三种解决方案,分别是手写构造函数、json_serializable 和 built_value。

 

使用所有三种方法将数据反序列化的调用很是类似。手写构造函数和 json_serializable 的代码行以下所示:

 

1final myObject=SimpleObject.fromJson(json.decode(aJsonString));

 

built_value 的反序列化调用以下所示:

 

1final myObject=serializers.deserializeWith(

2SimpleObject.serializer, json.decode(aJsonString));

 

真正的区别是,在该 “SimpleObject” 类中为您生成多少代码,以及这些代码有何做用。

 

手写构造函数

复杂性最低的方法:不为您生成代码。

您能够作本身想作的任何事,但您必须进行维护。

 

json_serializable

为您生成 fromJson 构造函数和 toJson 方法。

在构建应用以前,您须要在项目中加入若干个包,并使用 source_gen 生成部分文件。

对所生成的资源进行自定义可能会很棘手。

 

built_value

为序列化、不可变性、toString 方法、hashCode 属性等生成代码。这是具有诸多功能的重量级解决方案。

与 json_serializable 同样,您须要导入许多包并使用 source_gen。

拥有基于插件的可扩展序列化架构。

对实例建立和可变性等方面有看法。

 

正以下文所述,您适合使用哪一个内容库其实取决于项目详情,特别是项目大小和状态管理方法。对拥有一位维护者的兴趣项目来讲,手写构造函数颇有帮助,而由庞大分布式团队(他们须要不可变模型来保持逻辑清晰)构建的应用则会真正从 “built_value” 受益。

 

不过,如今咱们仍是从头开始介绍:将 JSON 从字符串解析为更方便使用的内存表示形式。不管您以后决定采起哪一种方法,相关流程中的这一步都是同样的。

 

 

解析 JSON

您可使用 dart:convert 库将 JSON 字符串转换为中间格式:

 

1import 'dart:convert';?

2

3try {

4??final parsed=json.decode(aJsonStr);

5} on FormatException catch (e) {?

6print("That string didn't look like Json.");

7} on NoSuchMethodError catch (e) {?

8??print('That string was null!');

9??}?

 

若是该字符串包含有效的 JSON,系统会返回对 List<dynamic> 或 Map<String, dynamic> 的动态引用,具体取决于 JSON 字符串是拥有数组仍是单个对象。对于整数列表之类的简单事项,如今已经差很少作完了。在使用以前,您可能会想建立第二个强类型的数据引用:

 

1??final dynamicListOfInts=json.decode(aJsonArrayOfInts);

2

3??// Create a strongly typed list with references to the data that are casted

4// immediately. This is probably the better approach for data model classes.

5final strongListOfInts=List<int>.from(dynamicListOfInts);?

6

7// Or create a strongly typed list with references that will be lazy-casted?

8// when used.

9final anotherStrongListOfInts=List<int>.from(dynamicListOfInts);

 

更复杂的有效负载才是有趣之处。将 Map<String, dynamic> 转化为实际模型对象时可能涉及转换默认值、null 和嵌套对象。若是您以后决定从新命名或添加/移除属性,不少方面可能会出错,并且须要更新不少麻烦的细节。

 

 

手写构造函数

咱们必须从某个地方开始,对吗?若是您有一个小应用,并且数据也不是很复杂,那么自行编写采用 Map<String, dynamic> 参数的工厂构造函数会大有帮助。例如,若是您要获取以下数据:

注:工厂构造函数连接

https://www.dartlang.org/guides/language/language-tour#factory-constructors

 

1{

2"aString": "Blah, blah, blah.",?

3"anInt": 1,

4"aDouble": 1.0,

5??"aListOfStrings": ["one", "two", "three"],

6??"aListOfInts": [1, 2, 3],?

7"aListOfDoubles": [1.0, 2.0, 3.0]

8??}

 

匹配类的代码可能以下所示:

 

1class SimpleObject {

2const SimpleObject({

3this.aString,

4??this.anInt,

5??this.aDouble,

6this.aListOfStrings,

7this.aListOfInts,

8this.aListOfDoubles,

9});

10

11final String aString;

12final int anInt;

13final double aDouble;?

14final List<String> aListOfStrings;

15final List<int> aListOfInts;?

16??final List<double> aListOfDoubles;

17

18factory SimpleObject.fromJson(Map<String, dynamic> json) {

19if (json==null) {

20throw FormatException("Null JSON provided to SimpleObject");

21}

22

23return SimpleObject(

24aString: json['aString'],

25anInt: json['anInt'],?

26aDouble: json['aDouble'],

27aListOfStrings: json['aListOfStrings'] !=null

28? List<String>.from(json['aListOfStrings'])

29: null,

30aListOfInts: json['aListOfInts'] !=null

31? List<int>.from(json['aListOfInts'])

32: null,?

33aListOfDoubles: json['aListOfDoubles'] !=null

34? List<double>.from(json['aListOfDoubles'])

35: null,

36);

37}

38}

 

而后按照以下方式使用已命名 fromJson 工厂构造函数:

 

1return SimpleObject(

2aString: json['aString'] "",

3anInt: json['anInt'] 0,

4aDouble: json['aDouble'] 1.0,

5aListOfStrings: List<String>.from(json['aListOfStrings'] []),

6aListOfInts: List<int>.from(json['aListOfInts'] []),

7aListOfDoubles: List<double>.from(json['aListOfDoubles'] []),?

8);

 

缺点在于,您须要手写大约 20 行构造函数代码,并且如今必须对其进行维护。随着您的应用扩大规模以及数据类数量开始增加为几十个,您可能会出现这样的想法:“唉,对这些 JSON 构造函数编码愈来愈无聊了,要是能够根据数据类的属性自动生成代码就行了。”

 

事实证实,借助 json_serializable 库,确实能够作到这一点。

 

 

使用 json_serializable

在介绍 json_serializable 以前,咱们须要转移一下话题,先简要讨论另外一个包。

 

Flutter 目前不支持映射,因此在其余上下文中可使用的某些技术(例如 Android JSON 库可以在运行时检查注解类)并不适用于 Flutter 开发者。不过,他们?能够?使用名为?source_gen?的 Dart 包。此包提供了实用工具和基本框架,以自动生成源代码。

 

source_gen 不会直接更新您的代码,而是在代码旁边另外建立新文件。按照惯例,其文件名中会有一个“g”,因此若是您的数据类存在于 model.dart 中,则 source_gen 会建立 model.g.dart。您可使用 part 关键字引用原来位置中的相应文件,而编译器会嵌入该文件。

 

json_serializable?包使用 source_gen API 来生成序列化代码,并会为您编写 fromJson 构造函数(及 toJson 方法)。

注:json_serializable 连接

https://pub.dartlang.org/packages/json_serializable

 

将其用于应用的基本流程以下所示:

 

将 json_serializable 和 json_annotation 包导入您的项目中。

如往常同样定义数据类。

在类定义中添加 @JsonSerializable 注解。

添加其余一些内容,将此数据类与为其建立的 json_serializable 代码关联起来。

运行 source_gen 以生成代码。

注:导入您的项目连接

https://github.com/dart-lang/json_serializable/tree/master/example

 

我会逐个介绍这些步骤。

 

将?json_serializable 包导入您的项目中

您能够在?Dart 包目录中找到 json_serializable。只需按照指示更新您的?pubspec.yaml?就能够了。

注:Dart 包目录连接

https://pub.dartlang.org/packages/json_serializable

更新您的?pubspec.yaml 连接

https://flutter.io/using-packages/#adding-a-package-dependency-to-an-app

 

定义数据类

这部分并无特别之处。使用基本属性和构造函数构建一个数据类。您计划序列化的属性应该是值类型或配合 json_serializable 使用的其余类。

 

1class SimpleObject { ? ?

2??SimpleObject({ ? ?

3??this.aString, ? ?

4this.anInt, ? ?

5??this.aDouble, ? ?

6this.aListOfStrings, ? ?

7this.aListOfInts, ? ?

8??this.aListOfDoubles, ? ?

9});

10?

11final String aString; ? ?

12??final int anInt; ? ?

13final double aDouble; ? ?

14??final List<String> aListOfStrings; ? ?

15??final List<int> aListOfInts; ? ?

16??final List<double> aListOfDoubles; ? ?

17}?

 

添加 @JsonSerializable 注注解

json_serializable 包只会为已使用 @JsonSerializable 注解标记过的数据类生成代码:

 

1??import 'package:json_annotation/json_annotation.dart';?

2

3@JsonSerializable ? ?

4class SimpleObject { ? ?

5... ? ?

6}

 

将所生成的代码与您的代码关联起来

接下来是将类定义与其相应 part 文件关联的三个变动:

1import 'package:json_annotation/json_annotation.dart';?

2

3??part 'simple_object.g.dart';

4

5??@JsonSerializable()

6??class SimpleObject extends Object with _$SimpleObjectSerializerMixin {

7SimpleObject({?

8this.aString,?

9this.anInt,

10this.aDouble,

11this.aListOfStrings,

12??this.aListOfInts,

13this.aListOfDoubles,

14});

15

16??final String aString;?

17final int anInt;

18final double aDouble;

19final List<String> aListOfStrings;

20final List<int> aListOfInts;

21final List<double> aListOfDoubles;

22

23??factory SimpleObject.fromJson(Map<String, dynamic> json)=>?

24_$SimpleObjectFromJson(json);

25??}

 

其中第一个是 part 声明,用于告知编译器嵌入 simple_object.g.dart(稍后会详细介绍相关内容)。而后是更新数据类定义以使用 mixin。最后是更新数据类以使用 fromJson 构造函数。后两个变动各自在所生成的文件中引用代码。

 

运行 source_gen

使用如下命令触发从您的项目文件夹生成代码:

 

flutter packages pub run build_runner build

 

完成后,原文件旁边会有一个名为 simple_object.g.dart 的新文件。文件内容以下所示:

 

1part of 'simple_object.dart';

2

3??SimpleObject _$SimpleObjectFromJson( ? ?

4Map<String, dynamic> json)=> ? ?

5??new SimpleObject( ? ?

6aString: json['aString'] as String, ? ?

7anInt: json['anInt'] as int, ? ?

8??aDouble: (json['aDouble'] as num)?.toDouble(), ? ?

9aListOfStrings: ? ?

10(json['aListOfStrings'] as List)?.map((e)=> e as String)?.toList(), ? ?

11aListOfInts: ? ?

12(json['aListOfInts'] as List)?.map((e)=> e as int)?.toList(), ? ?

13??aListOfDoubles: (json['aListOfDoubles'] as List) ? ?

14?.map((e)=> (e as num)?.toDouble()) ? ?

15?.toList());

16

17abstract class _$SimpleObjectSerializerMixin {?

18String get aString; ? ?

19int get anInt; ? ?

20??double get aDouble; ? ?

21??List<String> get aListOfStrings; ? ?

22List<int> get aListOfInts; ? ?

23List<double> get aListOfDoubles; ? ?

24Map<String, dynamic> toJson()=> <String,dynamic>{ ? ?

25'aString': aString, ? ?

26? ? ? ? ? ? ? ? ? ? 'anInt': anInt, ? ?

27? ? ? ? ? ? ? ? ? ? 'aDouble': aDouble, ? ?

28? ? ? ? ? ? ? ? ? ? 'aListOfStrings': aListOfStrings, ? ?

29? ? ? ? ? ? ? ? ? ? 'aListOfInts': aListOfInts, ? ?

30??'aListOfDoubles': aListOfDoubles ? ?

31}; ? ?

32}?

 

第一个方法是使用 SimpleObject 中的 fromJson 构造函数调用,而 mixin 类会为 SimpleObject 提供新 toJson 方法。两者都简单易用:

 

1final myObject=SimpleObject.fromJson(json.decode(jsonString)); ? ?

2final myJsonStr=json.encode(myObject.toJson());?

 

从数量方面来看,为 json_serializable 添加三行代码到 simple_object.dart 后,与使用其余方法相比,您能够少编写 20 行构造函数代码。在您想重命名或调整属性时,还能随时从新生成代码。此外,您能够得到咱们免费提供的 toJson 方法。这还不错吧。

 

但若是您想序列化至多种格式呢?或者不仅是 JSON 呢?若是您须要其余事物,例如不可变模型对象呢?对于这些用例,built_value 会派上用场。

 

 

使用 built_value

built_value(及其合做伙伴包 built_collection)远远不仅是自动序列化逻辑解决方案,其设计目的是帮助您建立充当值类型的数据类。为此,使用 built_value 建立的数据类实例是不可变的。您能够建立新实例(包括现有实例的副本),但一旦构建好实例,便没法更改其属性。

 

为作到这一点,built_value 使用在 json_serializable 中找到的相同源生成方法,但会建立更多代码。在为 built_value 类所生成的文件中,您会发现:

 

一个等式 (==) 运算符

一个 hashCode 属性

一个 toString 方法

一个序列化器类(若是您想要一个),下文会介绍更多相关内容

一个用于建立新实例的“构建器”类

 

即便是像 SimpleObject 这样的小类,加起来也有几百行,因此我不会在此赘述。实际类文件(您做为开发者编写的文件)以下所示:

 

1??import 'package:built_collection/built_collection.dart';

2import 'package:built_value/built_value.dart';

3import 'package:built_value/serializer.dart';

4

5??part 'simple_object.g.dart';

6

7abstract class SimpleObject

8??implements Built<SimpleObject, SimpleObjectBuilder> {?

9static Serializer<SimpleObject> get serializer=>?

10??_$SimpleObjectSerializer;?

11

12@nullable

13String get aString;

14

15@nullable

16??int get anInt;

17

18??@nullable

19double get aDouble;?

20

21@nullable

22BuiltList<String> get aListOfStrings;

23

24??@nullable

25??BuiltList<int> get aListOfInts;?

26

27??@nullable

28BuiltList<double> get aListOfDoubles;

29

30??SimpleObject._();

31

32??factory SimpleObject([updates(SimpleObjectBuilder b)])=

33_$SimpleObject;

34}

 

这个文件与咱们一开始使用的 SimpleObject 版本的区别在于:

 

像 json_serializable 同样声明 part 文件。

实行界面 Built<SimpleObject, SimpleObjectBuilder>。

添加了针对序列化器对象的静态 getter。

全部字段上都有是否为 Null 的注解。这些注解是可选项,但为了让此示例与其余相关示例匹配,我进行了添加。

添加了两个构造函数(一个不公开函数,一个工厂函数),并移除原来的函数。

SimpleObject 如今是抽象类了!

 

这个文件与咱们一开始使用的 SimpleObject 版本的区别在于:

 

咱们先看最后一点:SimpleObject 已变成抽象类。在所生成的文件中,built_value 定义了名为 _$SimpleObject 的 SimpleObject 子类,并提供许多新功能。在其中您会发现新的 hashCode 属性,以及与不可变性相关的新方法等等。每次您建立 SimpleObject 实例,其实是在后台得到 _$SimpleObject。但您永远不须要按派生类型引用它,所以您的应用代码仍会使用 SimpleObject 来声明和使用引用内容。

 

这是有可能实现的,由于您已经经过所生成的工厂构造函数完成对全新 SimpleObject 的实例化。您能够在上方文件的最后一行看到对其的引用。要开始使用,您须要传入一个方法,该方法在 SimpleObjectBuilder(即下方的“b”参数)上设置属性,并为您构建不可变对象实例:

 

1final SimpleObject myObject=SimpleObject((b)=> b

2??..aString='Blah, blah, blah'?

3..anInt=1

4..aDouble=1.0

5??..aListOfStrings=ListBuilder<String>(['one', 'two', 'three'])

6..aListOfInts=ListBuilder<int>([1, 2, 3])

7..aListOfDoubles=ListBuilder<double>([1.0, 2.0, 3.0])?

8);?

 

您也能够从新构建实例以得到现有实例的修改后副本:

 

1??final SimpleObject anotherObject=myObject.rebuild((b)=> b

2..aString="An updated value"?

3??);

 

您能够看到,经过使用下划线,SimpleObject 中的构造函数已变为不公开构造函数:

 

1SimpleObject._();

 

这样能够保证您应用的代码不直接将 SimpleObject 实例进行实例化。为得到实例,您必须使用工厂构造函数,该函数使用 SimpleObjectBuilder 并会一直产生 _$SimpleObject 子类的实例。

 

这很不错,但咱们明明讨论的是反序列化。

 

下面就介绍这一点!要对实例进行序列化和反序列化,您须要在应用中的某处添加一些代码(例如,建立一个名为 serializers.dart 的文件就是不错的方法):

 

1import 'package:built_collection/built_collection.dart';

2import 'package:built_value/serializer.dart';

3import 'package:built_value/standard_json_plugin.dart';

4??import 'simple_object.dart';?

5

6part 'serializers.g.dart';

7

8@SerializersFor(const [

9SimpleObject,

10??])

11

12??final Serializers serializers=

13(_$serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();

 

该文件作了两件事。第一,它使用 @SerializersFor 注解来指示 built_value 建立针对数据类列表的序列化器。在这个案例中只有一个数据类,因此列表很短。第二,它建立了一个名为序列化器的全局变量,该变量引用处理 built_value 类序列化的序列化器对象。使用状况以下:

 

1??final myObject=serializers.deserializeWith(

2??SimpleObject.serializer, json.decode(aJsonString));

3

4final String myJsonString=son.encode(serializers.serialize(myObject));

 

与 json_serializable 同样,因为所生成的代码能够为您完成繁重的工做,将某个对象转化为 JSON,或从 JSON 转化为其余格式,在大多数状况下仍只须要一行。须要注意的一点是来自 serializers.dart 的此代码:

 

1final Serializers serializers=

2??(_$serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();

 

built_value 旨在尽量进行扩展,而且包括用于定义自定义序列化格式的插件系统(例如,您能够编写一个来转换为 XML,并从 XML 或您本身的二进制格式转换。)在此示例中,我使用它来添加名为 StandardJsonPlugin 的插件,由于在默认状况下,built_value?不会使用您可能一般使用的基于地图的 JSON 格式。

 

相反,它使用基于列表的格式。例如,对带有 String、int 和 double 构件的简单对象进行序列化的方式以下:

 

1[ ? ?

2"SimpleObject", ? ?

3??"aString", ? ?

4??"Blah, blah, blah", ? ?

5??"anInt", ? ?

6??1, ? ?

7"aDouble", ? ?

8??2.0 ? ?

9]?

 

而不是这样:

 

1{ ? ?

2??"$": "SimpleObject", ? ?

3"aString": "Blah, blah, blah", ? ?

4??"anInt": 1, ? ?

5"aDouble": 2.0 ? ?

6??}

 

有一些缘由使 built_value 更喜欢使用基于列表的形式。因为空间有限,我会在包文档中进行说明。对于这个示例,您只须要了解您能够经过 StandardJsonPlugin 轻松使用基于地图的 JSON 序列化就够了,经典英语美文该插件是 built_value 包附带的一部分。

注:包文档连接

https://github.com/google/built_value.dart

 

 

结论

以上就是有关所有三项技术的重点内容。正如我在本文开头提到的,选择合适的技术主要是考虑您项目的范围、参与项目的人数,以及您对模型对象的其余需求。

 

下一步是开始编码,赶快前往?Flutter Dev Google Group、StackOverflow,或?The Boring Showl,让咱们知道您的进展!

注:Flutter Dev Google Group 连接

https://groups.google.com/forum/#!forum/flutter-dev

StackOverflow 连接

https://stackoverflow.com/questions/tagged/flutter

The Boring Showl 连接

https://www.youtube.com/watch?v=TiCA0CEePyE&list=PLOU2XLYxmsIK0r_D-zWcmJ1plIcDNnRkK&index=2

 

更多 AI 相关阅读:

·?Android Smart Linkify 支持机器学习

·?MnasNet:迈向移动端机器学习模型设计的自动化之路

·?将链接组学提升一个数量级

 

 


文章来源:https://blog.csdn.net/jILRvRTrc/article/details/82230103

相关文章
相关标签/搜索