- 原文地址: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 等代碼生成器庫。若是可能的話,我會在之後的文章中介紹它們。android
Fork 這個示例項目。它包含這篇博客中的全部代碼,你能夠對照着實踐一下。ios
讓咱們從一個簡單的 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
是鍵,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。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
對象中獲取值。
import 'dart:async' show Future;
import 'package:flutter/services.dart' show rootBundle;
import 'dart:convert';
import 'package:flutter_json/student_model.dart';
複製代碼
最後導入的是你的模型文件名。
Future<String> _loadAStudentAsset() async {
return await rootBundle.loadString('assets/student.json');
}
複製代碼
在這個特定項目中,咱們的 json 文件放在 assets 文件夾下,因此咱們必須這樣加載 json。但若是你的 json 文件放在雲端,你也能夠進行網絡調用。網絡調用不在咱們這篇文章的討論範圍內。
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 結構,但不是單一值的,它可能有一個值數組。
{
"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_
來運行它,它會完美運行。
如今若是咱們有一個像來自 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
運行它,你會對運行結果感到滿意的。
讓咱們檢查咱們的 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
。能夠在這裏 查看完整代碼。
如今讓咱們來看一下 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。代碼在 這裏.
這是 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、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。