Flutter | Json自動反序列化——json_serializable(附原始碼)
前言
Google推出flutter這樣一個新的高效能跨平臺(Android,ios)快速開發框架之後,被業界許多開發者所關注。我在接觸了flutter之後發現這個確實是一個好東西,好東西當然要和大家分享,對吧。
今天要跟大家分享的是Json反序列化的實現。相信做app的同學都會遇到這麼一個問題,在向伺服器請求資料後,伺服器往往會返回一段json字串。而我們要想更加靈活的使用資料的話需要把json字串轉化成物件。由於flutter只提供了json to Map。而手寫反序列化在大型專案中極不穩定,很容易導致解析失敗。所以今天給大家介紹的是flutter團隊推薦使用的 json_serializable
你將學到什麼
- flutter中如何解析json物件
- 如何使用自動生成工具生成程式碼
- 如何測試你的資料
開始json反序列化
第一步:建立mock資料
在實際開發過程中,我們可能會對之前的一些程式碼進行修改。當我們程式碼功能複雜並且量足夠大的時候,我們需要使用單元測試來保證新新增的程式碼不會影響之前所寫的程式碼。而伺服器的資料經常會變化,所以第一步當然是建立一個我們的mock資料啦。
abstract class JsonString{ static final String mockdata = ''' { "by" : "dhouston", "descendants" : 71, "id" : 8863, "kids" : [ 8952, 9224, 8917, 8884, 8887, 8943, 8869, 8958, 9005, 9671, 8940, 9067, 8908, 9055, 8865, 8881, 8872, 8873, 8955, 10403, 8903, 8928, 9125, 8998, 8901, 8902, 8907, 8894, 8878, 8870, 8980, 8934, 8876 ], "score" : 111, "time" : 1175714200, "title" : "My YC app: Dropbox - Throw away your USB drive", "type" : "story", "url" : "http://www.getdropbox.com/u/2/screencast.html" }'''; }
第二步:新增依賴
在pubspec.yaml中新增如下依賴
dependencies:
# Your other regular dependencies here
json_annotation: ^0.2.3
dev_dependencies:
# Your other dev_dependencies here
build_runner: ^0.9.0
json_serializable: ^0.5.4
這裡需要新增三個依賴,它們分別是:"json_annotation" "build_runner" 和 "json_serializable"。
請注意,yaml配置檔案對於縮排要求十分嚴格,下面的build_runner和json_serializable應該是與flutter_test平級的,千萬不要寫在flutter_test縮排後,這樣它會認為這兩個是flutter_test的子集目錄!
由於很多朋友在這一步遇到了問題,這裡貼出原始碼
image
第三步:根據json建立實體類
我們這裡根據上面的json資料寫好了一個dart的實體類
class Data{
final String by;
final int descendants;
final int id;
final List<int> kids;
final int score;
final int time;
final String title;
final String type;
final String url;
Data({this.by, this.descendants, this.id, this.kids, this.score, this.time,
this.title, this.type, this.url});
}
第四步:生成Json解析檔案
噹噹噹...!這裡開始就是重頭戲了!!
我們要使用JsonSerializable生成程式碼的話必須要在需要生成程式碼的實體類前添加註解@JsonSerializable(),而要使用這個註解我們必須引入json_annotation/json_annotation.dart這個包。
import 'package:json_annotation/json_annotation.dart';
@JsonSerializable()
class Data extends Object with _$DataSerializerMixin{
final String by;
final int descendants;
final int id;
final List<int> kids;
final int score;
final int time;
final String title;
final String type;
final String url;
Data({this.by, this.descendants, this.id, this.kids, this.score, this.time,
this.title, this.type, this.url});
}
這裡需要注意,flutter編碼規範dart檔名統一小寫,這樣可以避免很多問題。ok這樣實體類就建立完成啦。
那麼問題來了,應該如何生成程式碼呢?
還記得我們剛才新增的build_runner的依賴嗎,這時候我們就需要它來幫忙咯。
build_runner
build_runner是dart團隊提供的一個生成dart程式碼檔案的外部包。
我們在當前專案的目錄下執行flutter packages pub run build_runner build
image
執行成功後我們應該能在這個實體類的下面發現一個新的檔案
image
這個data.g.dart就是build_runner根據JsonSerializable生成的json解析檔案。剛生成完data.g.dart的會報錯,這是正常的! 我們來看看這個生成的dart檔案
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'data.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Data _$DataFromJson(Map<String, dynamic> json) => new Data(
by: json['by'] as String,
descendants: json['descendants'] as int,
id: json['id'] as int,
kids: (json['kids'] as List)?.map((e) => e as int)?.toList(),
score: json['score'] as int,
time: json['time'] as int,
title: json['title'] as String,
type: json['type'] as String,
url: json['url'] as String);
abstract class _$DataSerializerMixin {
String get by;
int get descendants;
int get id;
List<int> get kids;
int get score;
int get time;
String get title;
String get type;
String get url;
Map<String, dynamic> toJson() => <String, dynamic>{
'by': by,
'descendants': descendants,
'id': id,
'kids': kids,
'score': score,
'time': time,
'title': title,
'type': type,
'url': url
};
}
同志們請注意這段程式碼最上面的註釋"// GENERATED CODE - DO NOT MODIFY BY HAND"。你可千萬別手寫生成檔案啊哈哈哈哈。
這段程式碼生成實體類庫的一個part,在這個part中有一個抽象實體類的mixin,dart中使用基於mixin的繼承來解決單繼承的侷限性。
我們再來看 _$DataFromJson 方法。它接收了一個map:Map<String, dynamic>,並將這個Map裡的值對映為我們所需要的實體類物件。我們就可以使用這個方法,將存有json資料的map轉化為我們需要的實體類物件。
第五步:關聯實體類檔案
生成檔案只是給我們提供了map => dart 的方法,我們還需要在我們的實體類中關聯生成檔案,並在實體類中提供一個解析json的方法。
import 'package:json_annotation/json_annotation.dart';
part 'data.g.dart';
@JsonSerializable()
class Data extends Object with _$DataSerializerMixin{
final String by;
final int descendants;
final int id;
final List<int> kids;
final int score;
final int time;
final String title;
final String type;
final String url;
Data({this.by, this.descendants, this.id, this.kids, this.score, this.time,
this.title, this.type, this.url});
factory Data.fromJson(Map<String, dynamic> json) => _$DataFromJson(json);
}
為了使實體類檔案找到生成檔案,我們需要 part 'data.g.dart';並讓實體類混上_$DataSerializerMixin。最後提供一個工廠構造方法Data.fromJson,該方法實際呼叫生成檔案的DataFromJson方法。
這樣Json反序列化的工作就完成啦!
第六步:JSON反序列化
我們剛才實現了Map to Dart,可是我們需要的是json to dart。這時候就需要dart自帶的 dart:convert 來幫助我們了。
dart:convert
dart:convert是dart提供用於在不同資料表示之間進行轉換的編碼器和解碼器,能夠解析JSON和UTF-8。
也就是說我們需要先將json資料使用dart:convert轉成Map,我們就能通過Map轉為dart物件了。
使用方法
Map<String ,dynamic> map = json.decode("jsondata");
知道了如何將jsonString解析成map以後我們就能直接將json轉化為實體物件啦。
轉化方法
Data data = Data.fromJson(json.decode('jsondata'));
第七步:編寫單元測試
flutter給我們提供了單元測試,它的好處在於,我們想要驗證程式碼的正確性不用跑整個程式,每次只用跑一個單元測試檔案。而且養成習慣編寫單元測試以後,能夠保證以後在開發過程中快速精確定位錯誤,避免新加入的程式碼破壞老的程式碼引起專案崩潰。每次應用啟動前都會跑一遍單元測試以確保專案能夠正確執行。在實際開發中我們應該使用的mock資料來作為測試資料來源。
使用方法: 右鍵run這個測試檔案
import 'dart:convert';
import 'package:flutter_test/flutter_test.dart';
import 'package:wmkids/data/mockdata.dart';
import 'package:wmkids/data/data.dart';
void main(){
group('jsonparse test', (){
test('mockdata test', (){
Data data1 = Data.fromJson(json.decode(JsonString.mockdata));
expect(data1.url, 'http://www.getdropbox.com/u/2/screencast.html');
});
});
}
我們使用到了第一步建立的mock資料,並驗證了該json的url,假如我們解析正確這個單元測試將會通過。
image
這裡的group是一組測試,一個group中可以有多個test驗證我們的程式碼是否正確。
expect(data1,data2);會check我們的data1與data2的值是否相等,假如一樣的話就會通過測試。假如不一樣的話會告訴我們哪裡不一樣。
image
常用場景特殊處理辦法
物件巢狀場景下的json解析
在json中經常會使用巢狀資訊,我們在解析成dart檔案的時候需要解析成物件巢狀。在這種場景下需要將編寫步驟做一個調整。 我們需要在編寫實體類的時候就帶上工廠方法,因為物件存在依賴關係,先要保證子物件是serializable的才能保證父物件成功解析。
image
這裡提示有錯誤時正常的,然後再生成檔案。
自定義欄位
我們可以通過JsonKey自定義引數進行註釋並自定義引數來自定義各個欄位。例如:是否允許欄位為空等。注意,這裡不加任何JsonKey預設允許空json欄位。
例如:
image
這裡的json使用了“-”作為欄位名,而dart中只允許字母數字下劃線作為變數名。所以我們必須對它進行特殊處理。@JsonKey(name="Nicehash-CNHeavy")來解析map。
image
然後再生成檔案。
image
我們可以發現,生成檔案已經將map中的NicehashCNHeavy替換成了Nicehash-CNHeavy。
目前仍未解決的問題
- 泛型問題 在實際開發環境中json資訊一般會被巢狀在response中,我們想讓返回格式response中有一個泛型的data,目前json_serializable無法處理這一點。
github原始碼參考
作者:Vadaski 連結:https://www.jianshu.com/p/b307a377c5e8 來源:簡書 簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。