Flutter | Json自動(dòng)反序列化——json_serializable(附源碼)

前言

Google推出flutter這樣一個(gè)新的高性能跨平臺(tái)(Android,ios)快速開發(fā)框架之后,被業(yè)界許多開發(fā)者所關(guān)注。我在接觸了flutter之后發(fā)現(xiàn)這個(gè)確實(shí)是一個(gè)好東西,好東西當(dāng)然要和大家分享,對(duì)吧。

今天要跟大家分享的是Json反序列化的實(shí)現(xiàn)。相信做app的同學(xué)都會(huì)遇到這么一個(gè)問(wèn)題,在向服務(wù)器請(qǐng)求數(shù)據(jù)后,服務(wù)器往往會(huì)返回一段json字符串。而我們要想更加靈活的使用數(shù)據(jù)的話需要把json字符串轉(zhuǎn)化成對(duì)象。由于flutter只提供了json to Map。而手寫反序列化在大型項(xiàng)目中極不穩(wěn)定,很容易導(dǎo)致解析失敗。所以今天給大家介紹的是flutter團(tuán)隊(duì)推薦使用的 json_serializable 自動(dòng)反序列化。

你將學(xué)到什么

  • flutter中如何解析json對(duì)象
  • 如何使用自動(dòng)生成工具生成代碼
  • 如何測(cè)試你的數(shù)據(jù)

開始json反序列化

第一步:創(chuàng)建mock數(shù)據(jù)

在實(shí)際開發(fā)過(guò)程中,我們可能會(huì)對(duì)之前的一些代碼進(jìn)行修改。當(dāng)我們代碼功能復(fù)雜并且量足夠大的時(shí)候,我們需要使用單元測(cè)試來(lái)保證新添加的代碼不會(huì)影響之前所寫的代碼。而服務(wù)器的數(shù)據(jù)經(jīng)常會(huì)變化,所以第一步當(dāng)然是創(chuàng)建一個(gè)我們的mock數(shù)據(jù)啦。

這里使用了GITHUB/HackerNews的數(shù)據(jù)(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

這里需要添加三個(gè)依賴,它們分別是:"json_annotation" "build_runner" 和 "json_serializable"。

請(qǐng)注意,yaml配置文件對(duì)于縮進(jìn)要求十分嚴(yán)格,下面的build_runner和json_serializable應(yīng)該是與flutter_test平級(jí)的,千萬(wàn)不要寫在flutter_test縮進(jìn)后,這樣它會(huì)認(rèn)為這兩個(gè)是flutter_test的子集目錄!

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

image

第三步:根據(jù)json創(chuàng)建實(shí)體類

我們這里根據(jù)上面的json數(shù)據(jù)寫好了一個(gè)dart的實(shí)體類

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

我們?cè)谶@里使用了dart語(yǔ)法糖創(chuàng)建了構(gòu)造函數(shù)。具體請(qǐng)參考(https://www.dartlang.org/guides/language/language-tour#using-constructors)。

第四步:關(guān)聯(lián)實(shí)體類文件

我們需要在我們的實(shí)體類中關(guān)聯(lián)生成文件。

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的會(huì)報(bào)錯(cuò),這是正常的!因?yàn)槲覀冞€沒(méi)生成解析文件

  • 為了使實(shí)體類文件找到生成文件,我們需要 part 'data.g.dart'。

第五步:生成Json解析文件

當(dāng)當(dāng)當(dāng)...!這里開始就是重頭戲了!!

我們要使用JsonSerializable生成代碼的話必須要在需要生成代碼的實(shí)體類前添加注解@JsonSerializable(),而要使用這個(gè)注解我們必須引入json_annotation/json_annotation.dart這個(gè)包。

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編碼規(guī)范dart文件名統(tǒng)一小寫,這樣可以避免很多問(wèn)題。ok這樣實(shí)體類就創(chuàng)建完成啦。

那么問(wèn)題來(lái)了,應(yīng)該如何生成代碼呢?

還記得我們剛才添加的build_runner的依賴嗎,這時(shí)候我們就需要它來(lái)幫忙咯。

build_runner

build_runner是dart團(tuán)隊(duì)提供的一個(gè)生成dart代碼文件的外部包。

我們?cè)诋?dāng)前項(xiàng)目的目錄下運(yùn)行flutter packages pub run build_runner build

image

運(yùn)行成功后我們應(yīng)該能在這個(gè)實(shí)體類的下面發(fā)現(xiàn)一個(gè)新的文件

image

這個(gè)data.g.dart就是build_runner根據(jù)JsonSerializable生成的json解析文件。
我們來(lái)看看這個(gè)生成的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
    };

同志們請(qǐng)注意這段代碼最上面的注釋"http:// GENERATED CODE - DO NOT MODIFY BY HAND"。你可千萬(wàn)別手寫生成文件啊哈哈哈哈。

這段代碼生成實(shí)體類庫(kù)的一個(gè)part,在老版本part中有一個(gè)抽象實(shí)體類的mixin,dart中使用基于mixin的繼承來(lái)解決單繼承的局限性。

新版本中聲稱了兩個(gè)方法,fromJson和toJson方法,它們能干什么相信大家從名字上就能猜到了。

【對(duì)part感興趣的同學(xué)可以參考https://juejin.im/post/5b601f40e51d4519575a5036#heading-8 Dart | 淺析dart中庫(kù)的導(dǎo)入與拆分。

對(duì)mixin感興趣的同學(xué)可以在(https://www.dartlang.org/guides/language/language-tour#adding-features-to-a-class-mixins)了解更多關(guān)于mixin的知識(shí)?!?/p>

  • _$DataFromJson:它接收了一個(gè)map:Map<String, dynamic>,并將這個(gè)Map里的值映射為我們所需要的實(shí)體類對(duì)象。我們就可以使用這個(gè)方法,將存有json數(shù)據(jù)的map轉(zhuǎn)化為我們需要的實(shí)體類對(duì)象。
  • _$DataToJson:將調(diào)用此方法的對(duì)象直接根據(jù)字段映射成Map。

而這兩個(gè)都是私有方法,part讓兩個(gè)文件共享作用域與命名空間,所以我們需要將生成的方法暴露給外部。

然后我們?cè)倩氐綄?shí)體類中將 添加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);
}
  • 提供一個(gè)工廠構(gòu)造方法Data.fromJson,該方法實(shí)際調(diào)用生成文件的DataFromJson方法。
  • 提供一個(gè)toJson()序列化對(duì)象的方法,實(shí)際調(diào)用生成文件的_$DataToJson()方法,并將調(diào)用對(duì)象解析生成Map<String ,dynamic>。

這樣Json反序列化的工作就完成啦!

第六步:JSON反序列化

我們剛才實(shí)現(xiàn)了Map to Dart,可是我們需要的是json to dart。這時(shí)候就需要dart自帶的 dart:convert 來(lái)幫助我們了。

dart:convert

dart:convert是dart提供用于在不同數(shù)據(jù)表示之間進(jìn)行轉(zhuǎn)換的編碼器和解碼器,能夠解析JSON和UTF-8。

也就是說(shuō)我們需要先將json數(shù)據(jù)使用dart:convert轉(zhuǎn)成Map,我們就能通過(guò)Map轉(zhuǎn)為dart對(duì)象了。

使用方法

Map<String ,dynamic> map = json.decode("jsondata");

知道了如何將jsonString解析成map以后我們就能直接將json轉(zhuǎn)化為實(shí)體對(duì)象啦。

轉(zhuǎn)化方法

    Data data = Data.fromJson(json.decode('jsondata'));

第七步:編寫單元測(cè)試

flutter給我們提供了單元測(cè)試,它的好處在于,我們想要驗(yàn)證代碼的正確性不用跑整個(gè)程序,每次只用跑一個(gè)單元測(cè)試文件。而且養(yǎng)成習(xí)慣編寫單元測(cè)試以后,能夠保證以后在開發(fā)過(guò)程中快速精確定位錯(cuò)誤,避免新加入的代碼破壞老的代碼引起項(xiàng)目崩潰。每次應(yīng)用啟動(dòng)前都會(huì)跑一遍單元測(cè)試以確保項(xiàng)目能夠正確運(yùn)行。在實(shí)際開發(fā)中我們應(yīng)該使用的mock數(shù)據(jù)來(lái)作為測(cè)試數(shù)據(jù)來(lái)源。

使用方法
右鍵run這個(gè)測(cè)試文件

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

我們使用到了第一步創(chuàng)建的mock數(shù)據(jù),并驗(yàn)證了該json的url,假如我們解析正確這個(gè)單元測(cè)試將會(huì)通過(guò)。

image

這里的group是一組測(cè)試,一個(gè)group中可以有多個(gè)test驗(yàn)證我們的代碼是否正確。

expect(data1,data2);會(huì)check我們的data1與data2的值是否相等,假如一樣的話就會(huì)通過(guò)測(cè)試。假如不一樣的話會(huì)告訴我們哪里不一樣。

image

常用場(chǎng)景特殊處理辦法

對(duì)象嵌套場(chǎng)景下的json解析

在json中經(jīng)常會(huì)使用嵌套信息,我們?cè)诮馕龀蒬art文件的時(shí)候需要解析成對(duì)象嵌套。在這種場(chǎng)景下需要將編寫步驟做一個(gè)調(diào)整。
我們需要在編寫實(shí)體類的時(shí)候就帶上工廠方法,因?yàn)閷?duì)象存在依賴關(guān)系,先要保證子對(duì)象是serializable的才能保證父對(duì)象成功解析。

image

這里提示有錯(cuò)誤時(shí)正常的,然后再生成文件。

自定義字段

我們可以通過(guò)JsonKey自定義參數(shù)進(jìn)行注釋并自定義參數(shù)來(lái)自定義各個(gè)字段。例如:是否允許字段為空等。注意,這里不加任何JsonKey默認(rèn)允許空json字段。

例如:

image

這里的json使用了“-”作為字段名,而dart中只允許字母數(shù)字下劃線作為變量名。所以我們必須對(duì)它進(jìn)行特殊處理。@JsonKey(name="Nicehash-CNHeavy")來(lái)解析map。

image

然后再生成文件。

image

我們可以發(fā)現(xiàn),生成文件已經(jīng)將map中的NicehashCNHeavy替換成了Nicehash-CNHeavy。

泛型情況

github源碼參考

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

寫在最后

以上就是Flutter Json自動(dòng)反序列化的全部?jī)?nèi)容,文章若有不對(duì)之處歡迎各位大牛指正!關(guān)于泛型問(wèn)題若有更好的解決方案希望能在評(píng)論區(qū)告訴我-。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容