[譯]在 Flutter 中解析複雜的 JSON

我必須認可,在 Flutter/Dart 中使用 JSON 後,我一直想念 gson 的 Android 世界。當我開始使用 Flutter 中的 API 時,JSON 解析真的讓我很困擾。並且我敢肯定,它也讓不少初學者感到困惑。前端

咱們將在這篇博客中使用內置的 dart:convert 庫。這是最基本的解析方法,只有在你剛開始使用 Flutter 或者你正在寫一個小項目時才建議使用它。不過,瞭解一些 Flutter 中 JSON 解析的基礎知識很是重要。若是你精通這個,或者你須要寫更大的項目時,能夠考慮像 json_serializable 等代碼生成器庫。若是可能的話,我會在之後的文章中介紹它們。android

Fork 這個示例項目。它包含這篇博客中的全部代碼,你能夠對照着實踐一下。ios

JSON 結構 #1:簡單的 map

讓咱們從一個簡單的 JSON 結構開始 —— student.jsongit

{
  "id":"487349",
  "name":"Pooja Bhaumik",
  "score" : 1000
}
複製代碼

規則 #1: 肯定結構。Json 字符串將具備 Map(鍵值對)或 List of Maps。github

規則 #2:用花括號開始?這是 map。用方括號開始?那是 List of maps。json

student.json 明顯是 map(好比,id 是鍵,487349id 的值)。後端

讓咱們爲這個 json 結構作一個 PODO(Plain Old Dart Object?)文件。你能夠在示例項目的 student_model.dart 文件中找到這段代碼。數組

class Student{
  String studentId;
  String studentName;
  int studentScores;

  Student({
    this.studentId,
    this.studentName,
    this.studentScores
 });
}
複製代碼

Perfect! 是這樣嗎? 由於 json 映射和這個 PODO 文件之間沒有映射。甚至實體名稱也不匹配。 我知道我知道。 咱們尚未完成。咱們必須將這些類成員映射到 json 對象。爲此,咱們須要建立一個 factory 方法。根據 Dart 文檔,咱們在實現一個構造函數時使用 factory 關鍵字時,這個構造函數不會老是建立其類的新實例,而這正是咱們如今所須要的。安全

factory Student.fromJson(Map<String, dynamic> parsedJson){
    return Student(
      studentId: parsedJson['id'],
      studentName : parsedJson['name'],
      studentScores : parsedJson ['score']
    );
  }
複製代碼

在這裏,咱們建立了一個叫作 Student.fromJson 的工廠方法,用來簡單地反序列化你的 json。bash

我是一個小菜雞,能告訴我反序列化到底是什麼嗎?

固然。咱們首先要向你介紹序列化和反序列化。序列化 簡單來說就是把數據(可能在對象中)寫成字符串,反序列化 正好相反。它獲取原始數據並重建對象模型。在本文中,咱們主要討論反序列化部分。在第一部分中,咱們從 student.json 反序列化 json 字符串。

因此咱們的工廠方法也能夠稱爲咱們的轉換器方法。

還必須注意 fromJson 方法中的參數。它是一個 Map<String, dynamic> 這意味着它將 String 映射爲 dynamic 。這正是咱們須要識別它結構的緣由。若是這個 json 結構是一個映射列表,那麼這個參數會有所不一樣。

但爲何選擇動態呢? 讓咱們先看一下另外一個 json 結構來回答你的問題。

name 是一個 Map<String,String>,majors 是 String 和 List 的 Map,subject 是 String 和 List 的 Map。

由於鍵老是一個 string 而且值能夠是任何類型,因此咱們將它保持爲 dynamic 以保證安全。

這裏 檢查 student_model.dart 的完整代碼。

訪問對象

讓咱們寫 student_services.dart,它具備調用 Student.fromJson 的代碼,可以從 Student 對象中獲取值。

片斷 #1:imports

import 'dart:async' show Future;
import 'package:flutter/services.dart' show rootBundle;
import 'dart:convert';
import 'package:flutter_json/student_model.dart';
複製代碼

最後導入的是你的模型文件名。

片斷 #2:加載 Json Asset(可選)

Future<String> _loadAStudentAsset() async {
  return await rootBundle.loadString('assets/student.json');
}
複製代碼

在這個特定項目中,咱們的 json 文件放在 assets 文件夾下,因此咱們必須這樣加載 json。但若是你的 json 文件放在雲端,你也能夠進行網絡調用。網絡調用不在咱們這篇文章的討論範圍內。

片斷 #3:加載響應

Future loadStudent() async {
  String jsonString = await _loadAStudentAsset();
  final jsonResponse = json.decode(jsonString);
  Student student = new Student.fromJson(jsonResponse);
  print(student.studentScores);
}
複製代碼

loadStudent() 方法中, 第一行:從 assets 中加載原始 json 字符串。 第二行:解碼咱們獲得的 json 字符串。 第三行:如今咱們經過調用 Student.fromJson 方法反序列化解碼的 json 響應,這樣咱們如今可使用 Student 對象來訪問咱們的實體。 第四行:就像咱們在這裏作的同樣,咱們在 Student 類裏打印了 studentScores

檢查 Flutter 控制檯以查看打印的全部值。(在 Android Studio 中,它在運行選項下)

瞧!你剛剛完成了第一次 JSON 解析(或沒有)。 注意:請記住這裏的 3 個片斷,咱們將把它用於下一組 json 解析(只更改文件名和方法名),我不會在這裏重複代碼。但你能夠在示例項目中找到全部內容。

JSON 結構 #2:含有數組的簡單結構

如今咱們要征服一個和上面那個相似的 json 結構,但不是單一值的,它可能有一個值數組。

{
  "city": "Mumbai",
  "streets": [
    "address1",
    "address2"
  ]
}
複製代碼

因此在這個 address.json 中,咱們有一個包含簡單 String 值的 city 實體,但 streets 是一個 String 數組。 就我所知,Dart 並無數組這種數據類型,但它有 List,因此這裏 streets 是一個 List<String>

如今咱們要檢查一下 規則 #1 和 規則 #2。這絕對是一個 map,由於這是以花括號開頭的。streets 仍然是一個 List,但咱們稍後纔會考慮這個。

因此 address_model.dart 一開始看起來像是這樣的

class Address {
  final String city;
  final List<String> streets;

  Address({
    this.city,
    this.streets
  });
}
複製代碼

如今它是一個 map,咱們的 Address.fromJson 方法 仍然有一個 Map<String, dynamic> 參數。

factory Address.fromJson(Map<String, dynamic> parsedJson) {

  return new Address(
      city: parsedJson['city'],
      streets: parsedJson['streets'],
  );
}
複製代碼

如今經過添加上面提到的三個代碼片斷來構造 address_services.dart必須記住要放入正確的文件名和方法名。示例項目已經爲您構建了 _address_services.dart_

若是你如今運行它,你會發現一個小錯誤。

type 'List<dynamic>' is not a subtype of type 'List<String>'
複製代碼

我告訴你,這些錯誤幾乎在我 Dart 開發的每一步中都會出現。你也會遇到它們。那麼讓我解釋一下這是什麼意思。咱們正在請求 List<String> 但咱們獲得一個 List<dynamic> ,由於咱們的應用程序還沒法識別它的類型。

因此咱們必須把這個顯式地轉換成 List<String>

var streetsFromJson = parsedJson['streets'];
List<String> streetsList = new List<String>.from(streetsFromJson);
複製代碼

在這裏,咱們首先把變量映射到 streetsFromJson streets 實體。streetsFromJson 仍然是一個 List<dynamic>。如今咱們顯式地創造了一個 List<String> streetsList,它包含了 來自 streetsFromJson的全部元素。

這裏 檢查更新的方法。注意如今的返回語句。 如今你能夠用 _address_services.dart_ 來運行它,它會完美運行。

Json 結構 #3:簡單的嵌套結構

如今若是咱們有一個像來自 shape.json 的嵌套結構的話會怎樣呢?

{
  "shape_name":"rectangle",
  "property":{
    "width":5.0,
    "breadth":10.0
  }
}
複製代碼

這裏,property 包含一個對象而不是基本的數據類型。 那麼 POOD 看起來會是怎樣呢?

好啦,讓咱們先休息一會。 在 shape_model.dart 中,讓咱們先爲 Property 建一個類。

class Property{
  double width;
  double breadth;

  Property({
    this.width,
    this.breadth
});
}
複製代碼

如今讓咱們爲 Shape 建立一個類。我將這兩個類保存在同一個 Dart 文件中。

class Shape{
  String shapeName;
  Property property;

  Shape({
    this.shapeName,
    this.property
  });
}
複製代碼

注意第二個數據成員 property 就是咱們前面 Property 類的對象。

規則 #3:對於嵌套結構,首先建立類和構造函數,而後從底層添加工廠方法。

在底層上,個人意思是,首先咱們征服 _Property_ 類,而後咱們在 _Shape_ 類上再上一級。固然,這只是個人我的看法,不是 Flutter 規則。

factory Property.fromJson(Map<String, dynamic> json){
  return Property(
    width: json['width'],
    breadth: json['breadth']
  );
}
複製代碼

這是一個簡單的 map。

可是對於在 Shape 類中的工廠方法,咱們只能這樣作。

factory Shape.fromJson(Map<String, dynamic> parsedJson){
  return Shape(
    shapeName: parsedJson['shape_name'],
    property : parsedJson['property']
  );
}
複製代碼

property : parsedJson['property'] 首先,它會拋出一個類型不匹配錯誤 ——

type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'Property'
複製代碼

其次,嘿,咱們剛剛爲 Property 作了這個優雅的類,我沒有看到它在任何地方使用。

沒錯,咱們必須在這裏映射咱們的 Property 類。

factory Shape.fromJson(Map<String, dynamic> parsedJson){
  return Shape(
    shapeName: parsedJson['shape_name'],
    property: Property.fromJson(parsedJson['property'])
  );
}
複製代碼

因此基本上,咱們從 Property 類調用 Property.fromJson 方法,不管獲得什麼,咱們都將它映射到 property 實體。簡單!在 這裏 檢查你的代碼。

用你的 shape_services.dart 運行它,你會對運行結果感到滿意的。

JSON 結構 #4:含有 Lists 的嵌套結構

讓咱們檢查咱們的 product.json

{
  "id":1,
  "name":"ProductName",
  "images":[
    {
      "id":11,
      "imageName":"xCh-rhy"
    },
    {
      "id":31,
      "imageName":"fjs-eun"
    }
  ]
}
複製代碼

好的,如今咱們愈來愈深刻了。哇哦,我在裏面看到了一個對象列表。

是的,因此這個結構有一個對象列表,但它自己仍然是一個 map。(參考規則 #1規則 #2)。如今參考 規則 #3,讓咱們構造咱們的 product_model.dart

如今咱們來建立 ProductImage 這兩個類。 注意:_Product_ 會有一個數據成員,它是 _Image_ 的 List

class Product {
  final int id;
  final String name;
  final List<Image> images;

  Product({this.id, this.name, this.images});
}

class Image {
  final int imageId;
  final String imageName;

  Image({this.imageId, this.imageName});
}
複製代碼

Image 的工廠方法會很是簡單和基礎。

factory Image.fromJson(Map<String, dynamic> parsedJson){
 return Image(
   imageId:parsedJson['id'],
   imageName:parsedJson['imageName']
 );
}
複製代碼

這裏是 Product 的工廠方法

factory Product.fromJson(Map<String, dynamic> parsedJson){

  return Product(
    id: parsedJson['id'],
    name: parsedJson['name'],
    images: parsedJson['images']
  );
}
複製代碼

這裏明顯會拋出一個 runtime error

type 'List<dynamic>' is not a subtype of type 'List<Image>'
複製代碼

若是咱們這樣作,

images: Image.fromJson(parsedJson['images'])
複製代碼

這也是絕對錯誤的,它會當即引起錯誤,由於你沒法將 Image 對象分配給 List<Image>

因此咱們必須建立一個 List<Image> 而後將它分配給 images

var list = parsedJson['images'] as List;
print(list.runtimeType); //returns List<dynamic>
List<Image> imagesList = list.map((i) => Image.fromJson(i)).toList();
複製代碼

list 在這裏是一個 List。如今咱們經過調用 Image.fromJson 遍歷整個列表,並把 list 中的每一個對象映射到 Image 中,而後咱們將每一個 map 對象放入一個帶有 toList() 的新列表中,並將它存儲在 List<Image> imagesList。能夠在這裏 查看完整代碼。

JSON 結構 #5:map 列表

如今讓咱們來看一下 photo.json

[
  {
    "albumId": 1,
    "id": 1,
    "title": "accusamus beatae ad facilis cum similique qui sunt",
    "url": "http://placehold.it/600/92c952",
    "thumbnailUrl": "http://placehold.it/150/92c952"
  },
  {
    "albumId": 1,
    "id": 2,
    "title": "reprehenderit est deserunt velit ipsam",
    "url": "http://placehold.it/600/771796",
    "thumbnailUrl": "http://placehold.it/150/771796"
  },
  {
    "albumId": 1,
    "id": 3,
    "title": "officia porro iure quia iusto qui ipsa ut modi",
    "url": "http://placehold.it/600/24f355",
    "thumbnailUrl": "http://placehold.it/150/24f355"
  }
]
複製代碼

哦,規則 #1 和 規則 #2 能夠看出這不是一個 map,由於這個 json 字符串以方括號開頭。因此這是一個對象列表? 是的,這裏的對象是 Photo(或者你想稱之爲的任何東西)。

class Photo{
  final String id;
  final String title;
  final String url;

  Photo({
    this.id,
    this.url,
    this.title
}) ;

  factory Photo.fromJson(Map<String, dynamic> json){
    return new Photo(
      id: json['id'].toString(),
      title: json['title'],
      url: json['json'],
    );
  }
}
複製代碼

但它是一個 _Photo_ 列表,因此這意味着你必須建立一個包含 _List<Photo>_ 的類?

是的,我建議這樣。

class PhotosList {
  final List<Photo> photos;

  PhotosList({
    this.photos,
  });
}
複製代碼

同時請注意,這個 json 字符串是一個映射列表。所以,在咱們的工廠方法中,不會有一個 Map<String, dynamic> 參數,由於它是一個 List。這就是爲何首先要肯定結構。因此咱們的新參數是 List<dynamic>

factory PhotosList.fromJson(List<dynamic> parsedJson) {

    List<Photo> photos = new List<Photo>();

    return new PhotosList(
       photos: photos,
    );
  }
複製代碼

這樣會拋出一個錯誤。

Invalid value: Valid value range is empty: 0
複製代碼

嘿,由於咱們永遠不能使用 Photo.fromJson 方法。 若是咱們在列表初始化以後添加這行代碼會怎樣?

photos = parsedJson.map((i)=>Photo.fromJson(i)).toList();
複製代碼

與前面相同的概念,咱們沒必要把它映射到 json 字符串中的任何鍵,由於它是 List 而不是 map。代碼在 這裏.

JSON 結構 #6:複雜的嵌套結構

這是 page.json.

我會要求你解決這個問題。它已包含在示例項目中。你只須要爲此構建模型和服務文件。可是在給你提示以前我不會總結(若是你須要任何提示的話)。

規則 #1 and 規則 #2 同樣使用。首先肯定結構。這是一個 map。因此 1-5 的全部 json 結構都有用。

規則 #3 要求你先建立類和構造函數,而後從底層添加工廠方法。不過還有一個提示,還要記得從深層/底層添加類。例如,對於這個 json 結構,首先爲 Image 建立類,而後爲 DataAuthor 建立類,而後建立主類 Page。並以相同的順序添加工廠方法。

對於 ImageData 類,參考 Json 結構 #4。 對於 Author 類,參考 Json 結構 #3

給初學者的建議:在試驗任何新 asset 時,請記得在 pubspec.yaml 文件中聲明它。

這就是這篇 Fluttery 文章的內容。這篇文章可能不是最好的 JSON 解析文章(由於我還在學習不少東西),但我但願它能幫助你入門。


我弄錯了什麼嗎?在評論中提一下。我洗耳恭聽。

若是你學到了一兩件點知識,請儘量多地拍手 👏 以表示你的支持!這會鼓勵我寫更多的文章。

Hello World,我是 Pooja Bhaumik。一個有創意的開發人員和理性的設計師。你能夠在 LinkedinGitHubTwitter 上關注我?若是這對你來講太 social 了,若是你想和我談論對科技的想法,請發郵件到 pbhaumik26@gmail.com。

祝你度過美好的一天!

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索