使用 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

相關文章
相關標籤/搜索