Flutter如何JSON轉(zhuǎn)Model

在開(kāi)發(fā)中,服務(wù)端通常給我們返回的是JSON數(shù)據(jù)枢劝,我們需要將JSON數(shù)據(jù)轉(zhuǎn)成我們的模型對(duì)象來(lái)使用您旁。

在Flutter中,有幾種JSON轉(zhuǎn)模型的方式蚕脏,我們還是以豆瓣為例侦锯,來(lái)進(jìn)行一個(gè)演練。

一. 豆瓣數(shù)據(jù)

這里我們使用豆瓣的請(qǐng)求地址:https://douban.uieee.com/v2/movie/top250?start=0&count=20

在瀏覽器中請(qǐng)求挣棕,獲取到的數(shù)據(jù)如下:

  • 注意:這里我使用了一個(gè)格式化插件:FeHelper亲桥,所以結(jié)構(gòu)看起來(lái)很清晰

這個(gè)數(shù)據(jù)還是比較復(fù)雜的:

  • 如果我們希望在Flutter代碼中使用,直接將JSON轉(zhuǎn)成Map來(lái)使用也可以皂甘,但是非常麻煩悼凑,而且類(lèi)型會(huì)不容易確定璧瞬,并且不安全嗤锉。
  • 所以對(duì)于面向?qū)ο箝_(kāi)發(fā)的語(yǔ)言墓塌,我們通常都會(huì)將它轉(zhuǎn)成模型對(duì)象,之后使用一個(gè)個(gè)模型對(duì)象访诱。

我們一起來(lái)探究一下韩肝,目前Flutter中比較常見(jiàn)的將JSON轉(zhuǎn)成模型的方式。

二. 手動(dòng)轉(zhuǎn)化

JSON轉(zhuǎn)模型涡相,必然可以通過(guò)手動(dòng)來(lái)進(jìn)行轉(zhuǎn)化:

  • 優(yōu)點(diǎn):完全是自己可控的剩蟀,并且需要哪些字段就轉(zhuǎn)化哪些字段,對(duì)于不需要的丙号,忽略即可且预;并且繼承關(guān)系也會(huì)一目了然
  • 缺點(diǎn):麻煩,并且容易出錯(cuò);

下面是我之前針對(duì)上面的數(shù)據(jù)截酷,寫(xiě)的JSON轉(zhuǎn)Model的模型類(lèi):

class Person {
  String name;
  String avatarURL;

  Person.fromMap(Map<String, dynamic> json) {
    this.name = json["name"];
    this.avatarURL = json["avatars"]["medium"];
  }
}

class Actor extends Person {
  Actor.fromMap(Map<String, dynamic> json): super.fromMap(json);
}

class Director extends Person {
  Director.fromMap(Map<String, dynamic> json): super.fromMap(json);
}

int counter = 1;

class MovieItem {
  int rank;
  String imageURL;
  String title;
  String playDate;
  double rating;
  List<String> genres;
  List<Actor> casts;
  Director director;
  String originalTitle;

  MovieItem.fromMap(Map<String, dynamic> json) {
    this.rank = counter++;
    this.imageURL = json["images"]["medium"];
    this.title = json["title"];
    this.playDate = json["year"];
    this.rating = json["rating"]["average"];
    this.genres = json["genres"].cast<String>();
    this.casts = (json["casts"] as List<dynamic>).map((item) {
      return Actor.fromMap(item);
    }).toList();
    this.director = Director.fromMap(json["directors"][0]);
    this.originalTitle = json["original_title"];
  }
}

三. json_serializable

json_serializable是dart官方推薦和提供的JSON轉(zhuǎn)Model的方式:

  • 一個(gè)自動(dòng)化源代碼生成器來(lái)為你生成 JSON 序列化數(shù)據(jù)模板三热;
  • 由于序列化數(shù)據(jù)代碼不再需要手動(dòng)編寫(xiě)或者維護(hù)三幻,你可以將序列化 JSON 數(shù)據(jù)在運(yùn)行時(shí)的異常風(fēng)險(xiǎn)降到最低;

第一步:添加相關(guān)的依賴

依賴分為項(xiàng)目依賴(dependencies)抑堡,開(kāi)發(fā)依賴(dev_dependencies):

  • 注意:需要執(zhí)行flutter pub get確保我們的項(xiàng)目中有這些依賴
dependencies:
  json_annotation: ^3.0.1

dev_dependencies:
  json_serializable: ^3.2.5
  build_runner: ^1.8.0

第二步:以json_serializable 的方式創(chuàng)建模型類(lèi)

這里不以豆瓣數(shù)據(jù)為例,以一個(gè)簡(jiǎn)單的Json數(shù)據(jù)作為例子

  final jsonInfo = {
    "nickname": "coderwhy",
    "level": 18,
    "courses": ["語(yǔ)文", "數(shù)學(xué)", "英語(yǔ)"],
    "register_date": "2222-2-22",
    "computer": {
      "brand": "MackBook",
      "price": 1000
    }
  };

創(chuàng)建對(duì)應(yīng)的模型(以json_serializable 的方式首妖,創(chuàng)建完成后代碼是報(bào)錯(cuò)的

  1. part 'user.g.dart'
    這個(gè)是之后json_serializable會(huì)自動(dòng)幫助我們生成的文件
  2. @JsonSerializable()
    告訴json_serializable哪一個(gè)類(lèi)需要進(jìn)行轉(zhuǎn)換
  3. @JsonKey
    當(dāng)映射關(guān)系不一樣時(shí),可以指定映射關(guān)系
  4. 另外象踊,這里必須有我們的構(gòu)造方法
  5. 需要有對(duì)應(yīng)的工廠構(gòu)造器
    _$UserToJson(this)調(diào)用的該方法目前會(huì)報(bào)錯(cuò)棚壁,需要json_serializable來(lái)生成
  6. toString方法不是必須的,是待會(huì)兒進(jìn)行測(cè)試的

User類(lèi)的代碼:

import 'package:json_annotation/json_annotation.dart';
import 'model/computer.dart';

part 'user.g.dart';

@JsonSerializable()
class User {
  String name;
  String email;
  @JsonKey(name: "register_date")
  String registerDate;
  List<String> courses;
  Computer computer;

  User(this.name, this.email, this.registerDate, this.courses, this.computer);

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);

  @override
  String toString() {
    return 'User{name: $name, email: $email, registerDate: $registerDate, courses: $courses, computer: $computer}';
  }
}

Computer類(lèi)的代碼:

import 'package:json_annotation/json_annotation.dart';

part 'computer.g.dart';

@JsonSerializable()
class Computer {
  String brand;
  double price;

  Computer(this.brand, this.price);

  factory Computer.fromJson(Map<String, dynamic> json) => _$ComputerFromJson(json);
  Map<String, dynamic> toJson() => _$ComputerToJson(this);

  @override
  String toString() {
    return 'Computer{brand: $brand, price: $price}';
  }
}

第三步:生成JSON序列化代碼

在項(xiàng)目終端運(yùn)行下面的指令:

  • 該指令是生成一次JSON序列化的代碼
flutter pub run build_runner build

或運(yùn)行下面的指令:

  • 會(huì)監(jiān)聽(tīng)文件的改變史隆,重新生成JSON序列化的代碼
flutter pub run build_runner watch

第四步:測(cè)試代碼

final jsonInfo = {
  "nickname": "coderwhy",
  "level": 18,
  "courses": ["語(yǔ)文", "數(shù)學(xué)", "英語(yǔ)"],
  "register_date": "2222-2-22",
  "computer": {
    "brand": "MackBook",
    "price": 1000
  }
};

final user = User.fromJson(jsonInfo);
print(user);

更多資料逆害,請(qǐng)查看下面的資源:

  • dart:convertJsonCodec 文檔
  • Pub 中的 json_serializable package
  • GitHub 中的 json_serializable 例子

四. 網(wǎng)頁(yè)轉(zhuǎn)換

目前有一些網(wǎng)頁(yè)魄幕,可以直接將JSON轉(zhuǎn)成Model颖杏。

推薦網(wǎng)頁(yè):https://javiercbk.github.io/json_to_dart/

我們這里以網(wǎng)頁(yè)版本為例,非常簡(jiǎn)單:

注意:可能因?yàn)槎拱甑臄?shù)據(jù)過(guò)于復(fù)雜翼抠,所以在生成的時(shí)候發(fā)現(xiàn)少了一個(gè)Directors類(lèi)获讳,這里我重新復(fù)制對(duì)應(yīng)的JSON,再次生成了一下丐膝。

class MovieItem {
  Rating rating;
  List<String> genres;
  String title;
  List<Casts> casts;
  List<String> durations;
  int collectCount;
  String mainlandPubdate;
  bool hasVideo;
  String originalTitle;
  String subtype;
  List<Directors> directors;
  List<String> pubdates;
  String year;
  Avatars images;
  String alt;
  String id;

  MovieItem(
      {this.rating,
        this.genres,
        this.title,
        this.casts,
        this.durations,
        this.collectCount,
        this.mainlandPubdate,
        this.hasVideo,
        this.originalTitle,
        this.subtype,
        this.directors,
        this.pubdates,
        this.year,
        this.images,
        this.alt,
        this.id});

  MovieItem.fromJson(Map<String, dynamic> json) {
    rating =
    json['rating'] != null ? new Rating.fromJson(json['rating']) : null;
    genres = json['genres'].cast<String>();
    title = json['title'];
    if (json['casts'] != null) {
      casts = new List<Casts>();
      json['casts'].forEach((v) {
        casts.add(new Casts.fromJson(v));
      });
    }
    durations = json['durations'].cast<String>();
    collectCount = json['collect_count'];
    mainlandPubdate = json['mainland_pubdate'];
    hasVideo = json['has_video'];
    originalTitle = json['original_title'];
    subtype = json['subtype'];
    if (json['directors'] != null) {
      directors = new List<Directors>();
      json['directors'].forEach((v) {
        directors.add(new Directors.fromJson(v));
      });
    }
    pubdates = json['pubdates'].cast<String>();
    year = json['year'];
    images =
    json['images'] != null ? new Avatars.fromJson(json['images']) : null;
    alt = json['alt'];
    id = json['id'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    if (this.rating != null) {
      data['rating'] = this.rating.toJson();
    }
    data['genres'] = this.genres;
    data['title'] = this.title;
    if (this.casts != null) {
      data['casts'] = this.casts.map((v) => v.toJson()).toList();
    }
    data['durations'] = this.durations;
    data['collect_count'] = this.collectCount;
    data['mainland_pubdate'] = this.mainlandPubdate;
    data['has_video'] = this.hasVideo;
    data['original_title'] = this.originalTitle;
    data['subtype'] = this.subtype;
    if (this.directors != null) {
      data['directors'] = this.directors.map((v) => v.toJson()).toList();
    }
    data['pubdates'] = this.pubdates;
    data['year'] = this.year;
    if (this.images != null) {
      data['images'] = this.images.toJson();
    }
    data['alt'] = this.alt;
    data['id'] = this.id;
    return data;
  }
}

class Rating {
  int max;
  double average;
  Details details;
  String stars;
  int min;

  Rating({this.max, this.average, this.details, this.stars, this.min});

  Rating.fromJson(Map<String, dynamic> json) {
    max = json['max'];
    average = json['average'];
    details =
    json['details'] != null ? new Details.fromJson(json['details']) : null;
    stars = json['stars'];
    min = json['min'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['max'] = this.max;
    data['average'] = this.average;
    if (this.details != null) {
      data['details'] = this.details.toJson();
    }
    data['stars'] = this.stars;
    data['min'] = this.min;
    return data;
  }
}

class Details {
  int i1;
  int i2;
  int i3;
  int i4;
  int i5;

  Details({this.i1, this.i2, this.i3, this.i4, this.i5});

  Details.fromJson(Map<String, dynamic> json) {
    i1 = json['1'];
    i2 = json['2'];
    i3 = json['3'];
    i4 = json['4'];
    i5 = json['5'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['1'] = this.i1;
    data['2'] = this.i2;
    data['3'] = this.i3;
    data['4'] = this.i4;
    data['5'] = this.i5;
    return data;
  }
}

class Casts {
  Avatars avatars;
  String nameEn;
  String name;
  String alt;
  String id;

  Casts({this.avatars, this.nameEn, this.name, this.alt, this.id});

  Casts.fromJson(Map<String, dynamic> json) {
    avatars =
    json['avatars'] != null ? new Avatars.fromJson(json['avatars']) : null;
    nameEn = json['name_en'];
    name = json['name'];
    alt = json['alt'];
    id = json['id'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    if (this.avatars != null) {
      data['avatars'] = this.avatars.toJson();
    }
    data['name_en'] = this.nameEn;
    data['name'] = this.name;
    data['alt'] = this.alt;
    data['id'] = this.id;
    return data;
  }
}

class Directors {
  Avatars avatars;
  String nameEn;
  String name;
  String alt;
  String id;

  Directors({this.avatars, this.nameEn, this.name, this.alt, this.id});

  Directors.fromJson(Map<String, dynamic> json) {
    avatars =
    json['avatars'] != null ? new Avatars.fromJson(json['avatars']) : null;
    nameEn = json['name_en'];
    name = json['name'];
    alt = json['alt'];
    id = json['id'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    if (this.avatars != null) {
      data['avatars'] = this.avatars.toJson();
    }
    data['name_en'] = this.nameEn;
    data['name'] = this.name;
    data['alt'] = this.alt;
    data['id'] = this.id;
    return data;
  }
}

class Avatars {
  String small;
  String large;
  String medium;

  Avatars({this.small, this.large, this.medium});

  Avatars.fromJson(Map<String, dynamic> json) {
    small = json['small'];
    large = json['large'];
    medium = json['medium'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['small'] = this.small;
    data['large'] = this.large;
    data['medium'] = this.medium;
    return data;
  }
}

五. 編輯器插件

目前也有一些AndroidStudio或者VSCode的插件浑此,來(lái)幫助我們直接將JSON生成對(duì)應(yīng)的Model。

  • VSCode目前沒(méi)有找到比較好用的插件推薦
  • Android Studio推薦FlutterJsonBeanFactory

第一步:安裝插件

第二步:創(chuàng)建模型

右鍵新建文件:

給類(lèi)起一個(gè)名字紊馏,并且將JSON復(fù)制過(guò)去

第三步:使用生成的模型

創(chuàng)建完成后會(huì)生成對(duì)應(yīng)的模型,并且還會(huì)生成一個(gè)文件夾稀火,里面有生成模型過(guò)程的代碼赌朋,這里不再給出,代碼都是類(lèi)似的沛慢。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末团甲,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子躺苦,更是在濱河造成了極大的恐慌,老刑警劉巖嘀趟,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件愈诚,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡炕柔,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)陵刹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)欢嘿,“玉大人,你說(shuō)我怎么就攤上這事∠陨瑁” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵瑟枫,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我慷妙,道長(zhǎng),這世上最難降的妖魔是什么膝擂? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮狞山,結(jié)果婚禮上叉寂,老公的妹妹穿的比我還像新娘。我一直安慰自己屏鳍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布驳遵。 她就那樣靜靜地躺著降淮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪佳鳖。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天来庭,我揣著相機(jī)與錄音穿挨,去河邊找鬼。 笑死科盛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的厉萝。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼章母,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼翩剪!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起前弯,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎椿胯,沒(méi)想到半個(gè)月后剃根,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡廉油,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年苗傅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渣慕。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡逊桦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出强经,到底是詐尸還是另有隱情,我是刑警寧澤匿情,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站汁果,受9級(jí)特大地震影響玲躯,放射性物質(zhì)發(fā)生泄漏鲸伴。R本人自食惡果不足惜晋控,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一赡译、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蝌焚,春花似錦、人聲如沸只洒。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)距芬。三九已至,卻和暖如春框仔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背离斩。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留寻馏,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓操软,卻偏偏與公主長(zhǎng)得像宪祥,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蝗羊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容