前言
Google推出flutter這樣一個新的高性能跨平臺(Android,ios)快速開發框架之后,被業界許多開發者所關注。我在接觸了flutter之后發現這個確實是一個好東西,好東西當然要和大家分享,對吧。
今天要跟大家分享的是Json反序列化的實現。相信做app的同學都會遇到這么一個問題,在向服務器請求數據后,服務器往往會返回一段json字符串。而我們要想更加靈活的使用數據的話需要把json字符串轉化成對象。由于flutter只提供了json to Map。而手寫反序列化在大型項目中極不穩定,很容易導致解析失敗。所以今天給大家介紹的是flutter團隊推薦使用的 json_serializable 自動反序列化。
你將學到什么
- flutter中如何解析json對象
- 如何使用自動生成工具生成代碼
- 如何測試你的數據
開始json反序列化
第一步:創建mock數據
在實際開發過程中,我們可能會對之前的一些代碼進行修改。當我們代碼功能復雜并且量足夠大的時候,我們需要使用單元測試來保證新添加的代碼不會影響之前所寫的代碼。而服務器的數據經常會變化,所以第一步當然是創建一個我們的mock數據啦。
這里使用了GITHUB/HackerNews的數據(https://github.com/HackerNews/API)
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語法糖創建了構造函數。具體請參考(https://www.dartlang.org/guides/language/language-tour#using-constructors)。
第四步:關聯實體類文件
我們需要在我們的實體類中關聯生成文件。
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
運行成功后我們應該能在這個實體類的下面發現一個新的文件
這個data.g.dart就是build_runner根據JsonSerializable生成的json解析文件。
我們來看看這個生成的dart文件
// 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
};
同志們請注意這段代碼最上面的注釋"http:// 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感興趣的同學可以在(https://www.dartlang.org/guides/language/language-tour#adding-features-to-a-class-mixins)了解更多關于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自動反序列化的全部內容,文章若有不對之處歡迎各位大牛指正!關于泛型問題若有更好的解決方案希望能在評論區告訴我-。