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

DateBro發表於2018-07-29

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

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

我們將在這篇部落格中使用內建的 dart:convert 庫。這是最基本的解析方法,只有在你剛開始使用 Flutter 或者你正在寫一個小專案時才建議使用它。不過,瞭解一些 Flutter 中 JSON 解析的基礎知識非常重要。如果你精通這個,或者你需要寫更大的專案時,可以考慮像 json_serializable 等程式碼生成器庫。如果可能的話,我會在以後的文章中介紹它們。

Fork 這個示例專案。它包含這篇部落格中的所有程式碼,你可以對照著實踐一下。

JSON 結構 #1:簡單的 map

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

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

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

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

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。

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

當然。我們首先要向你介紹序列化和反序列化。序列化 簡單來講就是把資料(可能在物件中)寫成字串,反序列化 正好相反。它獲取原始資料並重建物件模型。在本文中,我們主要討論反序列化部分。在第一部分中,我們從 student.json 反序列化 json 字串。

所以我們的工廠方法也可以稱為我們的轉換器方法。

還必須注意 fromJson 方法中的引數。它是一個 Map<String, dynamic> 這意味著它將 String 對映為 dynamic 。這正是我們需要識別它結構的原因。如果這個 json 結構是一個對映列表,那麼這個引數會有所不同。

但為什麼選擇動態呢? 讓我們先看一下另一個 json 結構來回答你的問題。

[譯]在 Flutter 中解析複雜的 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前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章