- 原文地址:Parsing complex JSON in Flutter
- 原文作者:Poojã Bhaumik
- 譯文出自:掘金翻譯計劃
- 本文永久連結:github.com/xitu/gold-m…
- 譯者:DateBro
- 校對者:LeviDing
我必須承認,在 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
是鍵,487349
是 id
的值)。
讓我們為這個 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 結構來回答你的問題。
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
。
現在我們來建立 Product
和 Image
這兩個類。
注意:_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
建立類,然後為 Data
和 Author
建立類,然後建立主類 Page
。並以相同的順序新增工廠方法。
對於 Image
和 Data
類,參考 Json 結構 #4。
對於 Author
類,參考 Json 結構 #3
給初學者的建議:在試驗任何新 asset 時,請記得在 pubspec.yaml 檔案中宣告它。
這就是這篇 Fluttery 文章的內容。這篇文章可能不是最好的 JSON 解析文章(因為我還在學習很多東西),但我希望它能幫助你入門。
我弄錯了什麼嗎?在評論中提一下。我洗耳恭聽。
如果你學到了一兩件點知識,請儘可能多地拍手 ? 以表示你的支援!這會鼓勵我寫更多的文章。
Hello World,我是 Pooja Bhaumik。一個有創意的開發人員和理性的設計師。你可以在 Linkedin 或 GitHub 或 Twitter 上關注我?如果這對你來說太 social 了,如果你想和我談論對科技的想法,請發郵件到 pbhaumik26@gmail.com。
祝你度過美好的一天!
如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。