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數據啦。

這里使用了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的子集目錄!

由于很多朋友在這一步遇到了問題,這里貼出源碼

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});
}

我們在這里使用了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

image

運行成功后我們應該能在這個實體類的下面發現一個新的文件

image

這個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,假如我們解析正確這個單元測試將會通過。

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。

泛型情況

github源碼參考

https://github.com/Vadaski/Vadaski-flutter_note_book/tree/master/mecury_project/example/flutter_auto_json_parsing

寫在最后

以上就是Flutter Json自動反序列化的全部內容,文章若有不對之處歡迎各位大牛指正!關于泛型問題若有更好的解決方案希望能在評論區告訴我-

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,125評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,506評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,402評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,934評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,168評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,690評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,596評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,027評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,398評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,743評論 2 370

推薦閱讀更多精彩內容