iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 12
0
Mobile Development

30天手滑用Google Flutter解鎖Hybrid App成就系列 第 12

30天Flutter手滑系列 - JSON與序列化(JSON and serialization)

  • 分享至 

  • xImage
  •  

在閱讀了前一天的文章30天Flutter手滑系列 - 狀態管理 (State Management),似乎還有點懵懵懂懂。
沒關係,後面我會以實做專案的方式一步一步搭建專案,順便引入狀態管理的機制。今天就先來看看比較簡單的主題,認識JSON能為你做些什麼。

什麼是JSON?

JSON是一種常見的資料格式,以其淺顯易懂的編排方式來進行資料的交換。最早起源於JavaScript,現已被大多數程式語言支援。

進一步的說明可以參考JSON


什麼是序列化?

序列化又另外分成序列化跟反序列化兩種。
簡單來說,序列化的目的是為了把資料結構轉換成可被取用的格式,例如字串。相反地,反序列化是為了把字串轉換成其原本的資料結構。

進一步的說明可以參考序列化


序列化的兩種方法

1. 手動序列化

適合小專案使用。
手動序列化是使用dart:convert中內建的JSON解碼器。它將原始的JSON字串傳遞給JSON.decode(),然後回傳在Map<String, dynamic>中搜尋到的值。並不需要額外的設置,可直接使用。

透過官方範例來看一下dart:convert手動序列化JSON的用法:
這是一個簡單的JSON格式

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

- 序列化JSON inline:

通過調用jsonDecode()方法來解碼JSON,並把JSON字串當作參數傳進去。

Map<String, dynamic> user = jsonDecode(jsonString);

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

不幸的是,jsonDecode()只會回傳一個Map<String, dynamic>,這意味著你必須等到執行時才會知道數值的型別。如此一來就喪失了靜態語言的特性:型別安全(type safety)、自動補齊(autocompletion)以及最重要的編譯時異常(compile-time exceptions),造成我們的程式很容易出錯。
例如,當我們欲存取nameemail欄位,卻名稱打錯了,但是在編譯時卻不會報錯。

- 在Model Classes中序列化JSON

為了解決前面的問題,這裡可以引入一個model class。在這範例中為User,在這個Class內有:

  • 一個User.fromJson() 建構子,為了從map structure中,建構出一個新的User實例。
  • 一個toJson(),用來轉換一個User實例成map。

如此一來,這樣就具備了型別安全、自動補齊以及編譯時異常的特性。如果發生拼寫錯誤或是將欄位視為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,
    };
}

現在,反序列化的邏輯被移到Model內部,因此可以很容易地進行反序列化。

Map userMap = jsonDecode(jsonString);
var user = User.fromJson(userMap);

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

要序列化一個user,只需把該對象傳入jsonEncode這個方法中,不需要額外呼叫toJson()

String json = jsonEncode(user);

因此,為了程式碼的品質以及正確,加入Model是較理想的做法,如此一來就能確保序列化與反序列化能正確地運作。

2. 透過程式碼生成自動序列化

適合複雜度較高的大型專案。
隨著開發的專案功能漸多,整體複雜度變大,如果純用手動方式產出序列化資料會容易出錯,這時候官方推薦了兩套函式庫json_serializablebuilt_value。函式庫需要額外做一些設定,但是透過自動生成的方式可以避免一些格式錯誤。

接下來看一下官方推薦的json_serializable怎麼實作這部分。

- 設置json_serializable

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 pub get指令或是在編輯器點擊Package Get,確保這些相依的項目可以被使用。

- 以json_serializable方式創建Model

user.dart

import 'package:json_annotation/json_annotation.dart';

// 這允許User去存取這些產生的檔案中的私有成員
part 'user.g.dart';

// 這個修飾符是用來告訴生成器,這個Class是要來產生Model的
@JsonSerializable()

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

  String name;
  String email;

  // 這個facotry是必須要有的,為了從map創建一個新的User實例
  // 把整個map傳遞`_$UserFromJson()`
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);

  // `toJson`是用來限制即將進行序列化到JSON
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

有了這些設置,程式碼生成器會開始進行從JSON中編碼和解碼nameemail這兩個欄位。

如果有需要,也可以自定義命名策略。例如,如果使用的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,會有以下錯誤:
https://ithelp.ithome.com.tw/upload/images/20190919/201200288JFWYxe3IQ.png

這是很正常的,因為model還不存在,必須透過兩種方式去產生序列化的模板。

一次性生成(One-time code generation)

透過在專案的根目錄下執行flutter packages pub run build_runner build,這個方式是在我們需要為我們的model進行json序列化時候,產生一次性的建構。

持續生成(Generating code continuously)

另外可以透過watcher,在我們的根目錄下執行flutter package pub run build_runner watch,監聽我們專案中的變化,並在需要時自動建構必要的文件。


總結

序列化的方式在跟後端伺服器做資料交換時是很重要的一部分,制定好事前的準備,會減少實務上遇到不明錯誤發生的機會,有時候真的很常前後端為了找簡單的JSON錯誤,花上一些冤枉時間。


參考資料

https://flutter.dev/docs/development/data-and-backend/json


上一篇
30天Flutter手滑系列 - 狀態管理 (State Management)
下一篇
30天Flutter手滑系列 - Firebase 設定
系列文
30天手滑用Google Flutter解鎖Hybrid App成就30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言