Flutter JSON和序列化

2020-08-27 14:47 更新

種JSON序列化方法適合我?

本文介紹了使用JSON的兩個(gè)常規(guī)策略:

  • 手動(dòng)序列化和反序列化
  • 通過代碼生成自動(dòng)序列化和反序列化

不同的項(xiàng)目具有不同的復(fù)雜度和場景。對(duì)于較小項(xiàng)目,使用代碼生成器可能會(huì)過度。對(duì)于具有多個(gè)JSON model的復(fù)雜應(yīng)用程序,手動(dòng)序列化可能會(huì)比較重復(fù),并會(huì)很容易出錯(cuò)。

小項(xiàng)目手動(dòng)序列化

手動(dòng)JSON序列化是指使使用dart:convert中內(nèi)置的JSON解碼器。它將原始JSON字符串傳遞給JSON.decode() 方法,然后在返回的Map<String, dynamic>中查找所需的值。 它沒有外部依賴或其它的設(shè)置,對(duì)于小項(xiàng)目很方便。

當(dāng)您的項(xiàng)目變大時(shí),手動(dòng)編寫序列化邏輯可能變得難以管理且容易出錯(cuò)。如果您在訪問未提供的JSON字段時(shí)輸入了一個(gè)錯(cuò)誤的字段,則您的代碼將會(huì)在運(yùn)行時(shí)會(huì)引發(fā)錯(cuò)誤。

如果您的項(xiàng)目中JSON model并不多,并且希望快速測試一下,那么手動(dòng)序列化可能會(huì)很方便。

在大中型項(xiàng)目中使用代碼生成

代碼生成功能的JSON序列化是指通過外部庫為您自動(dòng)生成序列化模板。它需要一些初始設(shè)置,并運(yùn)行一個(gè)文件觀察器,從您的model類生成代碼。 例如,json_serializablebuilt_value就是這樣的庫。

這種方法適用于較大的項(xiàng)目。不需要手寫,如果訪問JSON字段時(shí)拼寫錯(cuò)誤,這會(huì)在編譯時(shí)捕獲的。代碼生成的不利之處在于它涉及到一些初始設(shè)置。另外,生成的源文件可能會(huì)在項(xiàng)目導(dǎo)航器會(huì)顯得混亂。

當(dāng)您有一個(gè)中型或大型項(xiàng)目時(shí),您可能想要使用代碼生成JSON序列化。


Flutter中是否有GSON / Jackson / Moshi?

簡單的回答是沒有.

這樣的庫需要使用運(yùn)行時(shí)反射,這在Flutter中是禁用的。運(yùn)行時(shí)反射會(huì)干擾Dart的_tree shaking_。使用_tree shaking_,我們可以在發(fā)版時(shí)“去除”未使用的代碼。這可以顯著優(yōu)化應(yīng)用程序的大小。

由于反射會(huì)默認(rèn)使用所有代碼,因此_tree shaking_會(huì)很難工作。這些工具無法知道哪些widget在運(yùn)行時(shí)未被使用,因此冗余代碼很難剝離。使用反射時(shí),應(yīng)用尺寸無法輕松的進(jìn)行優(yōu)化。

dartson呢?

dartson 使用了運(yùn)行時(shí)反射 runtime,所以不能在Flutter中使用它.

雖然我們不能在Flutter中使用運(yùn)行時(shí)反射,但有些庫為我們提供了類似易于使用的API,但它們是基于代碼生成的。這種方法在代碼生成庫部分有更詳細(xì)的介紹。


使用 dart:convert手動(dòng)序列化JSON

Flutter中基本的JSON序列化非常簡單。Flutter有一個(gè)內(nèi)置dart:convert庫,其中包含一個(gè)簡單的JSON編碼器和解碼器。

以下是一個(gè)簡單的user model的示例JSON。

{
  "name": "John Smith",
  "email": "john@example.com"
}

有了dart:convert,我們可以用兩種方式來序列化這個(gè)JSON model。我們來看看這兩種方法:

內(nèi)連序列化JSON

通過查看dart:轉(zhuǎn)換JSON文檔,我們發(fā)現(xiàn)可以通過調(diào)用JSON.decode方法來解碼JSON ,使用JSON字符串作為參數(shù)。

Map<String, dynamic> user = JSON.decode(json);

print('Howdy, ${user['name']}!');
print('We sent the verification link to ${user['email']}.');

不幸的是,JSON.decode()僅返回一個(gè)Map<String, dynamic>,這意味著我們直到運(yùn)行時(shí)才知道值的類型。 通過這種方法,我們失去了大部分靜態(tài)類型語言特性:類型安全、自動(dòng)補(bǔ)全和最重要的編譯時(shí)異常。這樣一來,我們的代碼可能會(huì)變得非常容易出錯(cuò)。

例如,當(dāng)我們?cè)L問name或email字段時(shí),我們輸入的很快,導(dǎo)致字段名打錯(cuò)了。但由于這個(gè)JSON在map結(jié)構(gòu)中,所以編譯器不知道這個(gè)錯(cuò)誤的字段名(譯者語:所以編譯時(shí)不會(huì)報(bào)錯(cuò))。

在模型類中序列化JSON

我們可以通過引入一個(gè)簡單的模型類(model class)來解決前面提到的問題,我們稱之為User。在User類內(nèi)部,我們有:

  • 一個(gè)User.fromJson 構(gòu)造函數(shù), 用于從一個(gè)map構(gòu)造出一個(gè) User實(shí)例 map structure
  • 一個(gè)toJson 方法, 將 User 實(shí)例轉(zhuǎn)化為一個(gè)map.

這樣,調(diào)用代碼現(xiàn)在可以具有類型安全、自動(dòng)補(bǔ)全字段(name和email)以及編譯時(shí)異常。如果我們將拼寫錯(cuò)誤或字段視為int類型而不是String, 那么我們的應(yīng)用程序就不會(huì)通過編譯,而不是在運(yùn)行時(shí)崩潰。

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,
    };
}

現(xiàn)在,序列化邏輯移到了模型本身內(nèi)部。采用這種新方法,我們可以非常容易地反序列化user。

Map userMap = JSON.decode(json);
var user = new User.fromJson(userMap);

print('Howdy, ${user.name}!');
print('We sent the verification link to ${user.email}.');

要序列化一個(gè)user,我們只是將該User對(duì)象傳遞給該JSON.encode方法。我們不需要手動(dòng)調(diào)用toJson這個(gè)方法,因?yàn)镴SON.encode已經(jīng)為我們做了。

String json = JSON.encode(user);

這樣,調(diào)用代碼就不用擔(dān)心JSON序列化了。但是,model類還是必須的。在生產(chǎn)應(yīng)用程序中,我們希望確保序列化正常工作。在實(shí)踐中,User.fromJson和User.toJson方法都需要單元測試到位,以驗(yàn)證正確的行為。

另外,實(shí)際場景中,JSON對(duì)象很少會(huì)這么簡單,嵌套的JSON對(duì)象并不罕見。

如果有什么能為我們自動(dòng)處理JSON序列化,那將會(huì)非常好。幸運(yùn)的是,有!


使用代碼生成庫序列化JSON

盡管還有其他庫可用,但在本教程中,我們使用了json_serializable package包。 它是一個(gè)自動(dòng)化的源代碼生成器,可以為我們生成JSON序列化模板。

由于序列化代碼不再由我們手寫和維護(hù),我們將運(yùn)行時(shí)產(chǎn)生JSON序列化異常的風(fēng)險(xiǎn)降至最低。

在項(xiàng)目中設(shè)置json_serializable

要包含json_serializable到我們的項(xiàng)目中,我們需要一個(gè)常規(guī)和兩個(gè)開發(fā)依賴項(xiàng)。簡而言之,開發(fā)依賴項(xiàng)是不包含在我們的應(yīng)用程序源代碼中的依賴項(xiàng)。

通過此鏈接可以查看這些所需依賴項(xiàng)的最新版本 。

pubspec.yaml

dependencies:
  # Your other regular dependencies here
  json_annotation: ^2.0.0

dev_dependencies:
  # Your other dev_dependencies here
  build_runner: ^1.0.0
  json_serializable: ^2.0.0

在您的項(xiàng)目根文件夾中運(yùn)行 flutter packages get (或者在編輯器中點(diǎn)擊 “Packages Get”) 以在項(xiàng)目中使用這些新的依賴項(xiàng).

以json_serializable的方式創(chuàng)建model類

讓我們看看如何將我們的User類轉(zhuǎn)換為一個(gè)json_serializable。為了簡單起見,我們使用前面示例中的簡化JSON model。

user.dart

import 'package:json_annotation/json_annotation.dart';

// user.g.dart 將在我們運(yùn)行生成命令后自動(dòng)生成
part 'user.g.dart';

///這個(gè)標(biāo)注是告訴生成器,這個(gè)類是需要生成Model類的
@JsonSerializable()

class User{
  User(this.name, this.email);

  String name;
  String email;
  //不同的類使用不同的mixin即可
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

有了這個(gè)設(shè)置,源碼生成器將生成用于序列化name和email字段的JSON代碼。

如果需要,自定義命名策略也很容易。例如,如果我們正在使用的API返回帶有_snake_case_的對(duì)象,但我們想在我們的模型中使用_lowerCamelCase_, 那么我們可以使用@JsonKey標(biāo)注:

/// Tell json_serializable that "registration_date_millis" should be
/// mapped to this property.
@JsonKey(name: 'registration_date_millis')
final int registrationDateMillis;


運(yùn)行代碼生成程序

json_serializable第一次創(chuàng)建類時(shí),您會(huì)看到與下圖類似的錯(cuò)誤。

IDE warning when the generated code for a model class does not exist
yet.

這些錯(cuò)誤是完全正常的,這是因?yàn)閙odel類的生成代碼還不存在。為了解決這個(gè)問題,我們必須運(yùn)行代碼生成器來為我們生成序列化模板。

There are two ways of running the code generator. 有兩種運(yùn)行代碼生成器的方法:

一次性生成

通過在我們的項(xiàng)目根目錄下運(yùn)行flutter packages pub run build_runner build,我們可以在需要時(shí)為我們的model生成json序列化代碼。 這觸發(fā)了一次性構(gòu)建,它通過我們的源文件,挑選相關(guān)的并為它們生成必要的序列化代碼。

雖然這非常方便,但如果我們不需要每次在model類中進(jìn)行更改時(shí)都要手動(dòng)運(yùn)行構(gòu)建命令的話會(huì)更好。

持續(xù)生成

使用_watcher_可以使我們的源代碼生成的過程更加方便。它會(huì)監(jiān)視我們項(xiàng)目中文件的變化,并在需要時(shí)自動(dòng)構(gòu)建必要的文件。我們可以通過flutter packages pub run build_runner watch在項(xiàng)目根目錄下運(yùn)行來啟動(dòng)_watcher_。

只需啟動(dòng)一次觀察器,然后并讓它在后臺(tái)運(yùn)行,這是安全的。

使用json_serializable模型

要通過json_serializable方式反序列化JSON字符串,我們不需要對(duì)先前的代碼進(jìn)行任何更改。

Map userMap = JSON.decode(json);
var user = new User.fromJson(userMap);

序列化也一樣。調(diào)用API與之前相同。

String json = JSON.encode(user);

有了json_serializable,我們可以在User類上忘記任何手動(dòng)的JSON序列化 。源代碼生成器創(chuàng)建一個(gè)名為user.g.dart的文件,它具有所有必需的序列化邏輯。 現(xiàn)在,我們不必編寫自動(dòng)化測試來確保序列化的正常工作 - 這個(gè)庫會(huì)確保序列化工作正常。


一些參考

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)