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

栏目: 后端 · 前端 · 发布时间: 7年前

内容简介:文 / Andrew Brogdon来源:谷歌开发者公众号在某种程度上,大多数应用都需要与外界互动,并从在线终端地址获取数据。借助 Dart 的 http 包,发出 HTTPS get 请求以获取天气预报或世界杯最终比分变得非常简单:

文 / Andrew Brogdon

来源:谷歌开发者公众号

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

1    import 'dart:async';    

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

3

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

5    if (response.statusCode == 200) {    

6    // use the data in response.body    

7    } else {    

8    // handle a failed request    

9    } 
复制代码

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

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

使用全部三种方法将数据反序列化的调用非常相似。手写构造函数和 json_serializable 的代码行如下所示:

1    final myObject = SimpleObject.fromJson(json.decode(aJsonString));
复制代码

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

1    final myObject = serializers.deserializeWith(  

2        SimpleObject.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 字符串转换为中间格式:

1    import 'dart:convert'; 

2

3    try {

4        final parsed = json.decode(aJsonStr);

5    } on FormatException catch (e) { 

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

7    } on NoSuchMethodError catch (e) { 

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

9    } 
复制代码

如果该字符串包含有效的 JSON,系统会返回对 List 或 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.

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

6

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

8    // when used.

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

复制代码

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

手写构造函数

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

注:工厂构造函数链接

www.dartlang.org/guides/lang…

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    }
复制代码

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

1    class SimpleObject {

2        const SimpleObject({

3            this.aString,

4            this.anInt,

5            this.aDouble,

6            this.aListOfStrings,

7            this.aListOfInts,

8            this.aListOfDoubles,

9        });

10

11        final String aString;

12        final int anInt;

13        final double aDouble; 

14        final List<String> aListOfStrings;

15        final List<int> aListOfInts; 

16        final List<double> aListOfDoubles;

17

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

19            if (json == null) {

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

21            }

22

23            return SimpleObject(

24                    aString: json['aString'],

25                    anInt: json['anInt'], 

26                    aDouble: json['aDouble'],

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

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

29                            : null,

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

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

32                            : null, 

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

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

35                            : null,

36                );

37            }

38        }
复制代码

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

1    return SimpleObject(

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

3        anInt: json['anInt'] ?? 0,

4        aDouble: json['aDouble'] ?? 1.0,

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

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

7        aListOfDoubles: 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 链接

pub.dartlang.org/packages/js…

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

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

如往常一样定义数据类。

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

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

运行 source_gen 以生成代码。

注:导入您的项目链接

github.com/dart-lang/j…

我会逐个介绍这些步骤。

将 json_serializable 包导入您的项目中

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

注:Dart 包目录链接

pub.dartlang.org/packages/js…

更新您的 pubspec.yaml 链接

flutter.io/using-packa…

定义数据类

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

1    class SimpleObject {    

2        SimpleObject({    

3            this.aString,    

4            this.anInt,    

5            this.aDouble,    

6            this.aListOfStrings,    

7            this.aListOfInts,    

8            this.aListOfDoubles,    

9        });   

10 

11        final String aString;    

12        final int anInt;    

13        final 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    

4    class SimpleObject {    

5    ...    

6    }
复制代码

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

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

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

2

3    part 'simple_object.g.dart';

4

5    @JsonSerializable()

6    class SimpleObject extends Object with _$SimpleObjectSerializerMixin {

7        SimpleObject({ 

8            this.aString, 

9            this.anInt,

10            this.aDouble,

11            this.aListOfStrings,

12            this.aListOfInts,

13            this.aListOfDoubles,

14        });

15

16        final String aString; 

17        final int anInt;

18        final double aDouble;

19        final List<String> aListOfStrings;

20        final List<int> aListOfInts;

21        final 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 的新文件。文件内容如下所示:

1    part of 'simple_object.dart';

2

3    SimpleObject _$SimpleObjectFromJson(    

4                    Map<String, dynamic> json) =>    

5            new SimpleObject(    

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

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

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

9                    aListOfStrings:    

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

11                    aListOfInts:    

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

17    abstract class _$SimpleObjectSerializerMixin { 

18        String get aString;    

19        int get anInt;    

20        double get aDouble;    

21        List<String> get aListOfStrings;    

22        List<int> get aListOfInts;    

23        List<double> get aListOfDoubles;    

24        Map<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 方法。二者都简单易用:

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

2    final 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';

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

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

4

5    part 'simple_object.g.dart';

6

7    abstract class SimpleObject

8            implements Built<SimpleObject, SimpleObjectBuilder> { 

9        static Serializer<SimpleObject> get serializer => 

10                _$SimpleObjectSerializer; 

11

12        @nullable

13        String get aString;

14

15        @nullable

16        int get anInt;

17

18        @nullable

19        double get aDouble; 

20

21        @nullable

22        BuiltList<String> get aListOfStrings;

23

24        @nullable

25        BuiltList<int> get aListOfInts; 

26

27        @nullable

28        BuiltList<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 来声明和使用引用内容。

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

1    final 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 中的构造函数已变为不公开构造函数:

1    SimpleObject._();
复制代码

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

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

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

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

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

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

4    import 'simple_object.dart'; 

5

6    part 'serializers.g.dart';

7

8    @SerializersFor(const [

9    SimpleObject,

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

4    final String myJsonString = son.encode(serializers.serialize(myObject));
复制代码

与 json_serializable 一样,由于所生成的代码可以为您完成繁重的工作,将某个对象转化为 JSON,或从 JSON 转化为其他格式,在大多数情况下仍只需要一行。需要注意的一点是来自 serializers.dart 的此代码:

1    final 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 包附带的一部分。

注:包文档链接

github.com/google/buil…

结论

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

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

注:Flutter Dev Google Group 链接

groups.google.com/forum/#!for…

StackOverflow 链接

stackoverflow.com/questions/t…

The Boring Showl 链接

www.youtube.com/watch?v=TiC…


以上所述就是小编给大家介绍的《使用 Flutter 反序列化 JSON 的一些选项》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

CSS3 Solutions

CSS3 Solutions

Marco Casario / Apress / 2012-8-13 / GBP 35.50

CSS3 brings a mass of changes, additions, and improvements to CSS across a range of new modules. Web designers and developers now have a whole host of new techniques up their sleeves, from working wit......一起来看看 《CSS3 Solutions》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换