Flutter JSON和序列化

2020-08-27 14:47 更新

種JSON序列化方法適合我?

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

  • 手動序列化和反序列化
  • 通過代碼生成自動序列化和反序列化

不同的項目具有不同的復雜度和場景。對于較小項目,使用代碼生成器可能會過度。對于具有多個JSON model的復雜應用程序,手動序列化可能會比較重復,并會很容易出錯。

小項目手動序列化

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

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

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

在大中型項目中使用代碼生成

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

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

當您有一個中型或大型項目時,您可能想要使用代碼生成JSON序列化。


Flutter中是否有GSON / Jackson / Moshi?

簡單的回答是沒有.

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

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

dartson呢?

dartson 使用了運行時反射 runtime,所以不能在Flutter中使用它.

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


使用 dart:convert手動序列化JSON

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

以下是一個簡單的user model的示例JSON。

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

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

內連序列化JSON

通過查看dart:轉換JSON文檔,我們發(fā)現(xiàn)可以通過調用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()僅返回一個Map<String, dynamic>,這意味著我們直到運行時才知道值的類型。 通過這種方法,我們失去了大部分靜態(tài)類型語言特性:類型安全、自動補全和最重要的編譯時異常。這樣一來,我們的代碼可能會變得非常容易出錯。

例如,當我們訪問name或email字段時,我們輸入的很快,導致字段名打錯了。但由于這個JSON在map結構中,所以編譯器不知道這個錯誤的字段名(譯者語:所以編譯時不會報錯)。

在模型類中序列化JSON

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

  • 一個User.fromJson 構造函數(shù), 用于從一個map構造出一個 User實例 map structure
  • 一個toJson 方法, 將 User 實例轉化為一個map.

這樣,調用代碼現(xiàn)在可以具有類型安全、自動補全字段(name和email)以及編譯時異常。如果我們將拼寫錯誤或字段視為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,
    };
}

現(xiàn)在,序列化邏輯移到了模型本身內部。采用這種新方法,我們可以非常容易地反序列化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}.');

要序列化一個user,我們只是將該User對象傳遞給該JSON.encode方法。我們不需要手動調用toJson這個方法,因為JSON.encode已經(jīng)為我們做了。

String json = JSON.encode(user);

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

另外,實際場景中,JSON對象很少會這么簡單,嵌套的JSON對象并不罕見。

如果有什么能為我們自動處理JSON序列化,那將會非常好。幸運的是,有!


使用代碼生成庫序列化JSON

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

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

在項目中設置json_serializable

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

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

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

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

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

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

user.dart

import 'package:json_annotation/json_annotation.dart';

// user.g.dart 將在我們運行生成命令后自動生成
part 'user.g.dart';

///這個標注是告訴生成器,這個類是需要生成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);
}

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

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

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


運行代碼生成程序

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

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

這些錯誤是完全正常的,這是因為model類的生成代碼還不存在。為了解決這個問題,我們必須運行代碼生成器來為我們生成序列化模板。

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

一次性生成

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

雖然這非常方便,但如果我們不需要每次在model類中進行更改時都要手動運行構建命令的話會更好。

持續(xù)生成

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

只需啟動一次觀察器,然后并讓它在后臺運行,這是安全的。

使用json_serializable模型

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

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

序列化也一樣。調用API與之前相同。

String json = JSON.encode(user);

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


一些參考

以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號