前言
Google推出flutter這樣一個新的高效能跨平臺(Android,ios)快速開發框架之後,被業界許多開發者所關注。我在接觸了flutter之後發現這個確實是一個好東西,好東西當然要和大家分享,對吧。
今天要跟大家分享的是Json反序列化的實現。相信做app的同學都會遇到這麼一個問題,在向伺服器請求資料後,伺服器往往會返回一段json字串。而我們要想更加靈活的使用資料的話需要把json字串轉化成物件。由於flutter只提供了json to Map。而手寫反序列化在大型專案中極不穩定,很容易導致解析失敗。所以今天給大家介紹的是flutter團隊推薦使用的 json_serializable 自動反序列化。
你將學到什麼
- flutter中如何解析json物件
- 如何使用自動生成工具生成程式碼
- 如何測試你的資料
開始json反序列化
第一步:建立mock資料
在實際開發過程中,我們可能會對之前的一些程式碼進行修改。當我們程式碼功能複雜並且量足夠大的時候,我們需要使用單元測試來保證新新增的程式碼不會影響之前所寫的程式碼。而伺服器的資料經常會變化,所以第一步當然是建立一個我們的mock資料啦。
這裡使用了GITHUB/HackerNews的資料(github.com/HackerNews/…)
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: ^1.2.0
dev_dependencies:
# Your other dev_dependencies here
build_runner: ^0.10.2
json_serializable: ^1.5.1
複製程式碼
這裡需要新增三個依賴,它們分別是:"json_annotation" "build_runner" 和 "json_serializable"。
請注意,yaml配置檔案對於縮排要求十分嚴格,下面的build_runner和json_serializable應該是與flutter_test平級的,千萬不要寫在flutter_test縮排後,這樣它會認為這兩個是flutter_test的子集目錄!
由於很多朋友在這一步遇到了問題,這裡貼出原始碼
第三步:根據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});
}
複製程式碼
我們在這裡使用了dart語法糖建立了建構函式。具體請參考(www.dartlang.org/guides/lang…)。
第四步:關聯實體類檔案
我們需要在我們的實體類中關聯生成檔案。
import 'package:json_annotation/json_annotation.dart';
part 'data.g.dart';
@JsonSerializable()
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;
@JsonKey(nullable: false)
final String url;
Data({this.by, this.descendants, this.id, this.kids, this.score, this.time,
this.title, this.type, this.url});
複製程式碼
剛寫完data.g.dart的會報錯,這是正常的!因為我們還沒生成解析檔案
- 為了使實體類檔案找到生成檔案,我們需要 part 'data.g.dart'。
第五步:生成Json解析檔案
噹噹噹...!這裡開始就是重頭戲了!!
我們要使用JsonSerializable生成程式碼的話必須要在需要生成程式碼的實體類前新增註解@JsonSerializable(),而要使用這個註解我們必須引入json_annotation/json_annotation.dart這個包。
import 'package:json_annotation/json_annotation.dart';
@JsonSerializable()
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});
}
複製程式碼
這裡需要注意,flutter編碼規範dart檔名統一小寫,這樣可以避免很多問題。ok這樣實體類就建立完成啦。
那麼問題來了,應該如何生成程式碼呢?
還記得我們剛才新增的build_runner的依賴嗎,這時候我們就需要它來幫忙咯。
build_runner
build_runner是dart團隊提供的一個生成dart程式碼檔案的外部包。
我們在當前專案的目錄下執行flutter packages pub run build_runner build
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'data.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Data _$DataFromJson(Map<String, dynamic> json) {
return 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);
}
Map<String, dynamic> _$DataToJson(Data instance) => <String, dynamic>{
'by': instance.by,
'descendants': instance.descendants,
'id': instance.id,
'kids': instance.kids,
'score': instance.score,
'time': instance.time,
'title': instance.title,
'type': instance.type,
'url': instance.url
};
複製程式碼
同志們請注意這段程式碼最上面的註釋"// GENERATED CODE - DO NOT MODIFY BY HAND"。你可千萬別手寫生成檔案啊哈哈哈哈。
這段程式碼生成實體類庫的一個part,在老版本part中有一個抽象實體類的mixin,dart中使用基於mixin的繼承來解決單繼承的侷限性。
新版本中聲稱了兩個方法,fromJson和toJson方法,它們能幹什麼相信大家從名字上就能猜到了。
【對part感興趣的同學可以參考https://juejin.im/post/5b601f40e51d4519575a5036#heading-8 Dart | 淺析dart中庫的匯入與拆分。
對mixin感興趣的同學可以在(www.dartlang.org/guides/lang…)瞭解更多關於mixin的知識。】
- _$DataFromJson:它接收了一個map:Map<String, dynamic>,並將這個Map裡的值對映為我們所需要的實體類物件。我們就可以使用這個方法,將存有json資料的map轉化為我們需要的實體類物件。
- _$DataToJson:將呼叫此方法的物件直接根據欄位對映成Map。
而這兩個都是私有方法,part讓兩個檔案共享作用域與名稱空間,所以我們需要將生成的方法暴露給外部。
然後我們再回到實體類中將 新增fromJson 和 toJson方法。
import 'package:json_annotation/json_annotation.dart';
@JsonSerializable()
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});
//反序列化
factory Data.fromJson(Map<String, dynamic> json) => _$DataFromJson(json);
//序列化
Map<String, dynamic> toJson() => _$DataToJson(this);
}
複製程式碼
- 提供一個工廠構造方法Data.fromJson,該方法實際呼叫生成檔案的DataFromJson方法。
- 提供一個toJson()序列化物件的方法,實際呼叫生成檔案的_$DataToJson()方法,並將呼叫物件解析生成Map<String ,dynamic>。
這樣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,假如我們解析正確這個單元測試將會通過。
這裡的group是一組測試,一個group中可以有多個test驗證我們的程式碼是否正確。
expect(data1,data2);會check我們的data1與data2的值是否相等,假如一樣的話就會通過測試。假如不一樣的話會告訴我們哪裡不一樣。
常用場景特殊處理辦法
物件巢狀場景下的json解析
在json中經常會使用巢狀資訊,我們在解析成dart檔案的時候需要解析成物件巢狀。在這種場景下需要將編寫步驟做一個調整。 我們需要在編寫實體類的時候就帶上工廠方法,因為物件存在依賴關係,先要保證子物件是serializable的才能保證父物件成功解析。
這裡提示有錯誤時正常的,然後再生成檔案。自定義欄位
我們可以通過JsonKey自定義引數進行註釋並自定義引數來自定義各個欄位。例如:是否允許欄位為空等。注意,這裡不加任何JsonKey預設允許空json欄位。
例如:
這裡的json使用了“-”作為欄位名,而dart中只允許字母數字下劃線作為變數名。所以我們必須對它進行特殊處理。@JsonKey(name="Nicehash-CNHeavy")來解析map。 然後再生成檔案。 我們可以發現,生成檔案已經將map中的NicehashCNHeavy替換成了Nicehash-CNHeavy。泛型情況
github原始碼參考
寫在最後
以上就是Flutter Json自動反序列化的全部內容,文章若有不對之處歡迎各位大牛指正!關於泛型問題若有更好的解決方案希望能在評論區告訴我^-^。
之後我會更新一系列flutter乾貨喜歡的話可以關注我或者給我一個好評哦!我會更新更有動力的。