在 Flutter 上从 Youtube api 解码 Json
Decode Json from Youtube api on Flutter
我打电话给 Youtube API 并得到这个 Json:
"kind": "youtube#videoListResponse",
"etag": "\"XI7nbFXulYBIpL0ayR_gDh3eu1k/s7-xmHXpuqQxYzDp_wxhm59K4LE\"",
"pageInfo": {
"totalResults": 1,
"resultsPerPage": 1
},
"items": [
{
"kind": "youtube#video",
"etag": "\"XI7nbFXulYBIpL0ayR_gDh3eu1k/pajQ7iBy-7A0V_owifxkw-Kbw-Y\"",
"id": "7lCDEYXw3mM",
"snippet": {
"publishedAt": "2012-06-20T23:12:38.000Z",
"channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw",
"title": "Google I/O 101: Q&A On Using Google APIs",
"description": "Antonio Fuentes speaks to us and takes questions on working with Google APIs and OAuth 2.0.",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/7lCDEYXw3mM/default.jpg",
"width": 120,
"height": 90
},
"medium": {
"url": "https://i.ytimg.com/vi/7lCDEYXw3mM/mqdefault.jpg",
"width": 320,
"height": 180
},
"high": {
"url": "https://i.ytimg.com/vi/7lCDEYXw3mM/hqdefault.jpg",
"width": 480,
"height": 360
}
但现在我想解析它并只得到 3 个节点:
- 标题
- 描述
- 默认缩略图url
确实我得到了 Json 响应并且可以在日志中看到它,但是每次尝试解析它时都失败了。
这是我的代码:
final response = await http.get(
'https://www.googleapis.com/youtube/v3/videos?id=HEREGOESMYAPIKEY&part=snippet&id=T0Jqdjbed40');
final parsed = json.decode(response.body).cast<Map<String, dynamic>>();
String title = parsed['items']['snippet']['title'];
print(title);
String description = parsed['items']['snippet']['description'];
print(description);
String thumbnail = parsed['items']['snippet']['thumbnails']['default']['url'];
print(thumbnail);
您尝试的方法不适用于 Dart,这不是 javascript。 Dart 有非常强大的类型系统,这很棒。您正在尝试为定义为 String
的变量赋值,但您定义的响应是动态的,因此 Dart 无法验证赋值。 items也是数组,没有这样的key items->snippet.
执行此操作的正确方法是创建模型定义,它将处理反序列化并提供访问您感兴趣的属性的便捷方式。
class YoutubeResponse {
String kind;
String etag;
String nextPageToken;
String regionCode;
List<Item> items;
YoutubeResponse(
{this.kind,
this.etag,
this.nextPageToken,
this.regionCode,
this.items});
Map<String, dynamic> toJson() => {
'kind': kind,
'etag': etag,
'nextPageToken': nextPageToken,
'regionCode': regionCode,
'items': items,
};
factory YoutubeResponse.fromJSON(Map<String, dynamic> YoutubeResponseJson) {
var list = YoutubeResponseJson['items'] as List;
List<Item> itemsList = list.map((i) => Item.fromJSON(i)).toList();
return new YoutubeResponse(
kind: YoutubeResponseJson['kind'],
etag: YoutubeResponseJson['etag'],
nextPageToken: YoutubeResponseJson['nextPageToken'],
regionCode: YoutubeResponseJson['regionCode'],
mPageInfo: pageInfo.fromJSON(YoutubeResponseJson['pageInfo']),
items: itemsList);
}
}
class Item {
String kind;
String etag;
Id id;
Snippet snippet;
Item({this.kind, this.etag, this.id, this.snippet});
Map<String, dynamic> toJson() => {
'kind': kind,
'etag': etag,
'id': id,
'snippet': snippet,
};
factory Item.fromJSON(Map<String, dynamic> ItemJson) {
return Item(
kind: ItemJson['kind'],
etag: ItemJson['etag'],
id: Id.fromJSON(ItemJson['id']),
snippet: Snippet.fromJSON(ItemJson['snippet']),
);
}
}
class Snippet {
String publishedAt;
String channelId;
String title;
String description;
Thumbnails thumbnails;
String channelTitle;
String liveBroadcastContent;
Snippet(
{this.publishedAt,
this.channelId,
this.title,
this.description,
this.thumbnails,
this.channelTitle,
this.liveBroadcastContent});
Map<String, dynamic> toJson() => {
'publishedAt': publishedAt,
'channelId': channelId,
'title': title,
'description': description,
'thumbnails': thumbnails,
'channelTitle': channelTitle,
'liveBroadcastContent': liveBroadcastContent,
};
factory Snippet.fromJSON(Map<String, dynamic> SnippetJson) {
return Snippet(
publishedAt: SnippetJson['publishedAt'],
channelId: SnippetJson['channelId'],
title: SnippetJson['title'],
description: SnippetJson['description'],
thumbnails: Thumbnails.fromJSON(SnippetJson['thumbnails']) ,
channelTitle: SnippetJson['channelTitle'],
liveBroadcastContent: SnippetJson['liveBroadcastContent'],
);
}
}
class Medium {
int height;
int width;
String url;
Medium({this.height, this.width, this.url});
Map<String, dynamic> toJson() => {
'height': height,
'width': width,
'url': url,
};
factory Medium.fromJSON(Map<String, dynamic> MediumJson) {
return Medium(
height: MediumJson['height'],
width: MediumJson['width'],
url: MediumJson['url'],
);
}
}
class High {
int height;
int width;
String url;
High({this.height, this.width, this.url});
Map<String, dynamic> toJson() => {
'height': height,
'width': width,
'url': url,
};
factory High.fromJSON(Map<String, dynamic> HighJson) {
return High(
height: HighJson['height'],
width: HighJson['width'],
url: HighJson['url'],
);
}
}
class Default {
int height;
int width;
String url;
Default({this.height, this.width, this.url});
Map<String, dynamic> toJson() => {
'height': height,
'width': width,
'url': url,
};
factory Default.fromJSON(Map<String, dynamic> defaultJson) {
return Default(
height: defaultJson['height'],
width: defaultJson['width'],
url: defaultJson['url'],
);
}
}
class Thumbnails {
Default mDefault;
Medium medium;
High high;
Thumbnails({this.mDefault, this.medium, this.high});
var data = JsonEncoder().convert("");
Map<String, dynamic> toJson() => {
'default': mDefault,
'medium': medium,
'high': high,
};
factory Thumbnails.fromJSON(Map<String, dynamic> ThumbnailsJson) {
return Thumbnails(
mDefault: Default.fromJSON(ThumbnailsJson['default']),
medium: Medium.fromJSON(ThumbnailsJson['medium']),
high: High.fromJSON(ThumbnailsJson['high']),
);
}
}
现在我们已经描述了 JSON 的格式,我们希望它很容易解析:
YoutubeResponse parsedResponse =
YoutubeResponse.fromJSON(json.decode(response.body));
然后您可以通过 parsedResponse.items
访问项目,然后遍历它们并获取标题、描述等。
为了进一步回答 @Shaddy(有效),这里是我的 class 调用网络
class VideoNetwork {
static Future<List<Item>> fetchPost() async {
final response =
await http.get(<URL for Youtube Videos>);
if (response.statusCode == 200) {
// If the call to the server was successful, parse the JSON
return compute(parseVideos, response.body);
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load post');
}
}
static List<Item> parseVideos(String responseBody) {
YoutubeResponse response =
YoutubeResponse.fromJSON(json.decode(responseBody));
return response.items.toList();
}
}
回答问题可能有点晚了。
如果您想学习如何解析复杂的 json 数据,请使用这些文章。
1. Parsing JSON in the background - Flutter cookbook
和
Parsing complex JSON in Flutter- medium.com by Pooja Bhaumik
仅供参考 - Youtube 数据 api 响应采用嵌套 Json 格式
我使用了另一种方法,不推荐用于大型应用程序(我只是在玩弄它,这仅适用于上述类型 json 树)
使用异步函数检索和解析 Json 数据。
final String dataUrl = "YOUR-JSON-URL";
Future<String> getdet() async {
var response = await http.get(Uri.encodeFull(dataUrl), headers: {"Accept": "application/json"});
if (response.statusCode == 200) {
var responseBody = json.decode(response.body);
convertedData = responseBody["items"];
} else {
throw Exception('Failed to Load Data');
}
"items " is the array starting point
之后您可以在 Widget 中使用它
Widget recentWidget(){
return ListView.builder(
itemCount: convertedData == null ? 0 : recent.length,
itemBuilder: (BuildContext context, int index, ) {
return Column(
children: <Widget>[
Card(
child: Column(
children: <Widget>[
new Image.network(recent[index]["snippet"]["thumbnails"]["medium"]["url"]),
new ListTile(
title: new Text(recent[index]["snippet"]["title"]),
subtitle: Text("This is Subtitle"),
},
dense: true,
),
],
),
)
],
);
},shrinkWrap: true,
physics: ClampingScrollPhysics(),
)
}
希望对您有所帮助。
@Sh1d0w 的回答很好,但是对于那些不太了解 flutter 本身的人来说,它缺乏内容。我不得不说谢谢 Sh1d0w 因为你给了我创建我自己的代码来使用 youtube url.
获取数据的基础
我使用 SearchDelegate 结合了他的答案以显示结果,我仍然需要添加代码来显示 next/previous 页面结果(youtube 按页面只给出 50 个结果)但要回答你的问题这是代码。
最后你将能够得到以下内容:
发布时间
频道编号
标题
描述
缩略图(默认、高、中)
直播内容
videoId
如果您想要 json 中的任何其他数据,您需要在 Class 代码段中添加您想要的内容,以便稍后返回和使用。
由于我使用的是在 snapshot.data object 中提供数据的 futureBuilder,您将需要使用这种声明来获取每个 属性:
snapshot.data[index].title
snapshot.data[index].description
snapshot.data[index].thumbnails.high.url
** 在代码中,您会看到 appTheme.text 等内容,它们只是颜色变量,请根据您的颜色更改它们。
** 像 sizeBuild() 或 fontSizeBuild() 这样的函数是我为我的案例创建的函数,你只需要删除这些行并根据你的需要写任何数字
common_youtubeApi.dart
import 'dart:convert';
class YoutubeResponse{
//!-1st level parameters of youtube api for playlist
//!-https://developers.google.com/youtube/v3/docs/playlistItems/list#response
String kind;
String etag;
String nextPageToken;
String prevPageToken;
String regionCode;
List<Item> items;
//change the default values for the obtained values from url
YoutubeResponse({
this.kind,
this.etag,
this.nextPageToken,
this.prevPageToken,
this.regionCode,
this.items
});
//Json decode and make a dart object called Map
Map<String, dynamic> toJson() => {
'kind': kind,
'etag': etag,
'nextPageToken': nextPageToken,
'prevPageToken': prevPageToken,
'regionCode': regionCode,
'items': items,
};
factory YoutubeResponse.fromJSON(Map<String, dynamic> YoutubeResponseJson){
var list = YoutubeResponseJson['items'] as List;
List<Item> itemsList = list.map((i)=> Item.fromJSON(i)).toList();
return new YoutubeResponse(
kind: YoutubeResponseJson['kind'],
etag: YoutubeResponseJson['etag'],
nextPageToken: YoutubeResponseJson['nextPageToken'],
prevPageToken: YoutubeResponseJson['prevPageToken'],
regionCode: YoutubeResponseJson['regionCode'],
// mPageInfo: pageInfo.fromJSON(YoutubeResponseJson['pageInfo']),
items: itemsList
);
}
}
//---------Create an single video item
class Item{
String kind;
String etag;
String id;
Snippet snippet;
Item({
this.kind, this.etag, this.id, this.snippet
});
Map<String, dynamic> toJson() => {
'kind': kind,
'etag': etag,
'id': id,
'snippet': snippet,
};
factory Item.fromJSON(Map<String, dynamic> ItemJson) {
return Item(
kind: ItemJson['kind'],
etag: ItemJson['etag'],
id: ItemJson['id'],
snippet: Snippet.fromJSON(ItemJson['snippet']),
);
}
}
class Snippet {
String publishedAt;
String channelId;
String title;
String description;
Thumbnails thumbnails;
String channelTitle;
String liveBroadcastContent;
String videoId;
Snippet(
{this.publishedAt,
this.channelId,
this.title,
this.description,
this.thumbnails,
this.channelTitle,
this.liveBroadcastContent,
this.videoId,
});
Map<String, dynamic> toJson() => {
'publishedAt': publishedAt,
'channelId': channelId,
'title': title,
'description': description,
'thumbnails': thumbnails,
'channelTitle': channelTitle,
'liveBroadcastContent': liveBroadcastContent,
'videoId': videoId,
};
factory Snippet.fromJSON(Map<String, dynamic> snippetJson) {
return Snippet(
publishedAt: snippetJson['publishedAt'],
channelId: snippetJson['channelId'],
title: snippetJson['title'],
description: snippetJson['description'],
thumbnails: Thumbnails.fromJSON(snippetJson['thumbnails']) ,
channelTitle: snippetJson['channelTitle'],
liveBroadcastContent: snippetJson['liveBroadcastContent'],
videoId: snippetJson['resourceId']['videoId'],
);
}
}
class Medium {
int height;
int width;
String url;
Medium({this.height, this.width, this.url});
Map<String, dynamic> toJson() => {
'height': height,
'width': width,
'url': url,
};
factory Medium.fromJSON(Map<String, dynamic> MediumJson) {
return Medium(
height: MediumJson['height'],
width: MediumJson['width'],
url: MediumJson['url'],
);
}
}
class High {
int height;
int width;
String url;
High({this.height, this.width, this.url});
Map<String, dynamic> toJson() => {
'height': height,
'width': width,
'url': url,
};
factory High.fromJSON(Map<String, dynamic> HighJson) {
return High(
height: HighJson['height'],
width: HighJson['width'],
url: HighJson['url'],
);
}
}
class Default {
int height;
int width;
String url;
Default({this.height, this.width, this.url});
Map<String, dynamic> toJson() => {
'height': height,
'width': width,
'url': url,
};
factory Default.fromJSON(Map<String, dynamic> defaultJson) {
return Default(
height: defaultJson['height'],
width: defaultJson['width'],
url: defaultJson['url'],
);
}
}
class Thumbnails {
Default mDefault;
Medium medium;
High high;
Thumbnails({this.mDefault, this.medium, this.high});
var data = JsonEncoder().convert("");
Map<String, dynamic> toJson() => {
'default': mDefault,
'medium': medium,
'high': high,
};
factory Thumbnails.fromJSON(Map<String, dynamic> thumbnailsJson) {
return Thumbnails(
mDefault: Default.fromJSON(thumbnailsJson['default']),
medium: Medium.fromJSON(thumbnailsJson['medium']),
high: High.fromJSON(thumbnailsJson['high']),
);
}
}
searchList.dart
要导入的文件:
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:denApp/keys/app_keys.dart'; //put your keys always in a different file with gitignore
import 'package:url_launcher/url_launcher.dart'; //for links in each item
import 'package:denApp/Widgets/common_youtubeAPI.dart'; //here are the models of each data requested in the json to be used here
class DataSearch extends SearchDelegate<List>{
var nextPageToken;
var prevPageToken;
Future<void> _launched;
//ajax/http request for data
Future<List<Snippet>> getVideos(http.Client client) async {
YoutubeResponse parsedResponse;
final response = await client
.get('https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId=$youtubeTrickPlaylist&key=$youtubeAPIKEY&maxResults=50');
List<Snippet> videos = [];
parsedResponse = YoutubeResponse.fromJSON(json.decode(response.body));
for(var i=0; i < parsedResponse.items.length; i++ ){
videos.add(parsedResponse.items[i].snippet);
}
this.nextPageToken = parsedResponse.nextPageToken;
this.prevPageToken = parsedResponse.prevPageToken;
print(this.nextPageToken);
print(this.prevPageToken);
return videos;
}
//We use the launcher plugin to manage the click to go to the website, please visit the plugin web for info how to use it
Future<void> _launchInBrowser(String url) async {
print(url);
if (await canLaunch(url)) {
await launch(
url,
forceSafariVC: false,
forceWebView: false,
headers: <String, String>{'my_header_key': 'my_header_value'},
);
} else {
throw 'Could not launch $url';
}
}
//------------------------------------------------------
//--This part is to edit the colors and design of the searchDelegate widget, I have a separated file with the themes, colors which I call using appTheme, so you need to change all those variables with your own colors.
@override
ThemeData appBarTheme(BuildContext context) {
return ThemeData(
primaryColor: appTheme.text,
backgroundColor: appTheme.dark,
bottomAppBarColor: appTheme.dark,
canvasColor: appTheme.dark,
);
}
//-------------------------------------------------------------
//---Here we define how it will works the SearchDelegate and its icons/functions-----------
@override
List<Widget> buildActions(BuildContext context) {
// ----This is the icon which will delete whatever you write in your searchbar
return [IconButton(icon: Icon(Icons.clear), onPressed: () {
query="";
},)];
}
@override
Widget buildLeading(BuildContext context) {
// ---- This is the icon which will allow you to close the search delegate going to the back page.
return IconButton(icon: Icon(Icons.arrow_back), onPressed: (){
close(context, null);
});
}
@override
Widget buildResults(BuildContext context) {
// nothing here
throw UnimplementedError();
}
@override
Widget buildSuggestions(BuildContext context) {
// This is actually the place where you will do all the work when you receive the data from your future request. Since we are going to build something after we wait for the data, we need to use futureBuilder instead of builder only. In the future propiety you will put the function which will do the request to the api
return FutureBuilder<List<Snippet>>(
future: getVideos(http.Client()),
builder: (context, snapshot) {
//during the build you need to check the connection state so depending on what is happening you will show something , like a loader if it is waiting state, etc
switch (snapshot.connectionState){
case ConnectionState.none:
return Text('none', style: TextStyle(color:Colors.black));
case ConnectionState.active:
return Text('active', style: TextStyle(color:Colors.black));
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
default:
//after checking connection we need to check if we do not got null data from the request
if(snapshot.data != null){
var dt = snapshot.data.where((p)=> p.title.toLowerCase().contains(query.toLowerCase())).toList();
return dt.isEmpty? Center(child:Text("There was no results for your query", style: TextStyle(color: appTheme.text))) : ListView.builder(
itemCount: dt.length,
padding: EdgeInsets.zero,
itemBuilder: (context, index) {
if(query.isEmpty){
return Card(
margin: (index == (dt.length - 1)) ? EdgeInsets.only(top: sizeBuild(context, "height", 5), bottom: sizeBuild(context, "height", 15)) : EdgeInsets.only(top: sizeBuild(context, "height", 5)),
color: appTheme.bgLight.withOpacity(.1),
child: InkWell(
splashColor: appTheme.linkB.withAlpha(30),
onTap: () {
_launched = _launchInBrowser('https://www.youtube.com/watch?v=${snapshot.data[index].videoId}');
},
child: Container(
child: Row(
children: <Widget>[
Container(
width:sizeBuild(context, "width", 140),
child: Image.network("${snapshot.data[index].thumbnails.high.url}", fit: BoxFit.cover),
),
Container(
// color: Colors.red,
margin: EdgeInsets.symmetric(horizontal: sizeBuild(context, "width",10)),
width:sizeBuild(context, "width", 280),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(snapshot.data[index].title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle (
color: appTheme.text,
fontWeight: FontWeight.bold,
fontSize: fontSizeBuild(context, 'body')
),
),
SizedBox(
height: sizeBuild(context, "height", 5),
),
Text(snapshot.data[index].description,
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: TextStyle (
color: appTheme.text.withOpacity(.7),
fontSize: fontSizeBuild(context, 'small')
),
),
],
),
),
],
),
),
),
);
}else{
return Card(
margin: (index == (dt.length - 1)) ? EdgeInsets.only(top: sizeBuild(context, "height", 5), bottom: sizeBuild(context, "height", 15)) : EdgeInsets.only(top: sizeBuild(context, "height", 5)),
color: appTheme.bgLight.withOpacity(.1),
child: InkWell(
splashColor: appTheme.linkB.withAlpha(30),
onTap: () {
_launched = _launchInBrowser('https://www.youtube.com/watch?v=${snapshot.data[index].videoId}');
},
child: Container(
child: Row(
children: <Widget>[
Container(
width:sizeBuild(context, "width", 140),
child: Image.network("${snapshot.data[index].thumbnails.high.url}", fit: BoxFit.cover),
),
Container(
margin: EdgeInsets.symmetric(horizontal: sizeBuild(context, "width",10)),
width:sizeBuild(context, "width", 280),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(snapshot.data[index].title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle (
color: appTheme.text,
fontWeight: FontWeight.bold,
fontSize: fontSizeBuild(context, 'body')
),
),
SizedBox(
height: sizeBuild(context, "height", 20),
),
Text(snapshot.data[index].description,
maxLines: 5,
overflow: TextOverflow.ellipsis,
style: TextStyle (
color: appTheme.text.withOpacity(.7),
fontSize: fontSizeBuild(context, 'small')
),
),
],
),
),
],
),
),
),
);
}
},
);
}else{
//this is the widget to give if there is no data
return Center(child: Text("There was no data from the server", style: TextStyle(color:appTheme.text)));
}
}
},
);
}
}
你的代码片段中的问题比其他答案所暗示的要简单得多。
首先,您的 JSON 字符串可能有误,您漏掉了几件事。您可能还忘记了对 etag 值进行转义:"etag": "\\"XI7nbFXulYBIpL0ayR_gDh3eu1k/s7-xmHXpuqQxYzDp_wxhm59K4LE\\"",
。当然,所有这些都可能只是复制错误。
你 可以 创建额外的 类,如果你想长期维护你的代码,你绝对应该这样做。有代码生成器可以为您完成大部分工作。但是,您 不需要 创建那些巨大的 类,只是为了得到 JSON 的 3 个值。你也不需要在不同的隔离上旋转,JSON 很小(对于计算机),你不会注意到解析它的任何性能问题。
你采取的方法没有问题。这是一个简单有效的解决方案,非常适合非常简单的脚本和应用程序。
唯一的问题是你忘记了items
是一个列表,所以你需要先取那个列表中的第0个元素。修复之后,一切都按预期工作:parsed['items'][0]['snippet']['title']
.
您可以将此复制到 dartpad.dev 并自己尝试。
import 'dart:convert';
void main() {
final parsed = jsonDecode(responseBody);
String title = parsed['items'][0]['snippet']['title'];
String description = parsed['items'][0]['snippet']['description'];
String thumbnail = parsed['items'][0]['snippet']['thumbnails']['default']['url'];
print('title: $title');
print('description: $description');
print('thumbnail: $thumbnail');
}
const responseBody = '''{
"kind": "youtube#videoListResponse",
"etag": "\\"XI7nbFXulYBIpL0ayR_gDh3eu1k/s7-xmHXpuqQxYzDp_wxhm59K4LE\\"",
"pageInfo": {
"totalResults": 1,
"resultsPerPage": 1
},
"items": [
{
"kind": "youtube#video",
"etag": "\\"XI7nbFXulYBIpL0ayR_gDh3eu1k/pajQ7iBy-7A0V_owifxkw-Kbw-Y\\"",
"id": "7lCDEYXw3mM",
"snippet": {
"publishedAt": "2012-06-20T23:12:38.000Z",
"channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw",
"title": "Google I/O 101: Q&A On Using Google APIs",
"description": "Antonio Fuentes speaks to us and takes questions on working with Google APIs and OAuth 2.0.",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/7lCDEYXw3mM/default.jpg",
"width": 120,
"height": 90
},
"medium": {
"url": "https://i.ytimg.com/vi/7lCDEYXw3mM/mqdefault.jpg",
"width": 320,
"height": 180
},
"high": {
"url": "https://i.ytimg.com/vi/7lCDEYXw3mM/hqdefault.jpg",
"width": 480,
"height": 360
}
}
}
}
]
}''';
我打电话给 Youtube API 并得到这个 Json:
"kind": "youtube#videoListResponse",
"etag": "\"XI7nbFXulYBIpL0ayR_gDh3eu1k/s7-xmHXpuqQxYzDp_wxhm59K4LE\"",
"pageInfo": {
"totalResults": 1,
"resultsPerPage": 1
},
"items": [
{
"kind": "youtube#video",
"etag": "\"XI7nbFXulYBIpL0ayR_gDh3eu1k/pajQ7iBy-7A0V_owifxkw-Kbw-Y\"",
"id": "7lCDEYXw3mM",
"snippet": {
"publishedAt": "2012-06-20T23:12:38.000Z",
"channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw",
"title": "Google I/O 101: Q&A On Using Google APIs",
"description": "Antonio Fuentes speaks to us and takes questions on working with Google APIs and OAuth 2.0.",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/7lCDEYXw3mM/default.jpg",
"width": 120,
"height": 90
},
"medium": {
"url": "https://i.ytimg.com/vi/7lCDEYXw3mM/mqdefault.jpg",
"width": 320,
"height": 180
},
"high": {
"url": "https://i.ytimg.com/vi/7lCDEYXw3mM/hqdefault.jpg",
"width": 480,
"height": 360
}
但现在我想解析它并只得到 3 个节点:
- 标题
- 描述
- 默认缩略图url
确实我得到了 Json 响应并且可以在日志中看到它,但是每次尝试解析它时都失败了。
这是我的代码:
final response = await http.get(
'https://www.googleapis.com/youtube/v3/videos?id=HEREGOESMYAPIKEY&part=snippet&id=T0Jqdjbed40');
final parsed = json.decode(response.body).cast<Map<String, dynamic>>();
String title = parsed['items']['snippet']['title'];
print(title);
String description = parsed['items']['snippet']['description'];
print(description);
String thumbnail = parsed['items']['snippet']['thumbnails']['default']['url'];
print(thumbnail);
您尝试的方法不适用于 Dart,这不是 javascript。 Dart 有非常强大的类型系统,这很棒。您正在尝试为定义为 String
的变量赋值,但您定义的响应是动态的,因此 Dart 无法验证赋值。 items也是数组,没有这样的key items->snippet.
执行此操作的正确方法是创建模型定义,它将处理反序列化并提供访问您感兴趣的属性的便捷方式。
class YoutubeResponse {
String kind;
String etag;
String nextPageToken;
String regionCode;
List<Item> items;
YoutubeResponse(
{this.kind,
this.etag,
this.nextPageToken,
this.regionCode,
this.items});
Map<String, dynamic> toJson() => {
'kind': kind,
'etag': etag,
'nextPageToken': nextPageToken,
'regionCode': regionCode,
'items': items,
};
factory YoutubeResponse.fromJSON(Map<String, dynamic> YoutubeResponseJson) {
var list = YoutubeResponseJson['items'] as List;
List<Item> itemsList = list.map((i) => Item.fromJSON(i)).toList();
return new YoutubeResponse(
kind: YoutubeResponseJson['kind'],
etag: YoutubeResponseJson['etag'],
nextPageToken: YoutubeResponseJson['nextPageToken'],
regionCode: YoutubeResponseJson['regionCode'],
mPageInfo: pageInfo.fromJSON(YoutubeResponseJson['pageInfo']),
items: itemsList);
}
}
class Item {
String kind;
String etag;
Id id;
Snippet snippet;
Item({this.kind, this.etag, this.id, this.snippet});
Map<String, dynamic> toJson() => {
'kind': kind,
'etag': etag,
'id': id,
'snippet': snippet,
};
factory Item.fromJSON(Map<String, dynamic> ItemJson) {
return Item(
kind: ItemJson['kind'],
etag: ItemJson['etag'],
id: Id.fromJSON(ItemJson['id']),
snippet: Snippet.fromJSON(ItemJson['snippet']),
);
}
}
class Snippet {
String publishedAt;
String channelId;
String title;
String description;
Thumbnails thumbnails;
String channelTitle;
String liveBroadcastContent;
Snippet(
{this.publishedAt,
this.channelId,
this.title,
this.description,
this.thumbnails,
this.channelTitle,
this.liveBroadcastContent});
Map<String, dynamic> toJson() => {
'publishedAt': publishedAt,
'channelId': channelId,
'title': title,
'description': description,
'thumbnails': thumbnails,
'channelTitle': channelTitle,
'liveBroadcastContent': liveBroadcastContent,
};
factory Snippet.fromJSON(Map<String, dynamic> SnippetJson) {
return Snippet(
publishedAt: SnippetJson['publishedAt'],
channelId: SnippetJson['channelId'],
title: SnippetJson['title'],
description: SnippetJson['description'],
thumbnails: Thumbnails.fromJSON(SnippetJson['thumbnails']) ,
channelTitle: SnippetJson['channelTitle'],
liveBroadcastContent: SnippetJson['liveBroadcastContent'],
);
}
}
class Medium {
int height;
int width;
String url;
Medium({this.height, this.width, this.url});
Map<String, dynamic> toJson() => {
'height': height,
'width': width,
'url': url,
};
factory Medium.fromJSON(Map<String, dynamic> MediumJson) {
return Medium(
height: MediumJson['height'],
width: MediumJson['width'],
url: MediumJson['url'],
);
}
}
class High {
int height;
int width;
String url;
High({this.height, this.width, this.url});
Map<String, dynamic> toJson() => {
'height': height,
'width': width,
'url': url,
};
factory High.fromJSON(Map<String, dynamic> HighJson) {
return High(
height: HighJson['height'],
width: HighJson['width'],
url: HighJson['url'],
);
}
}
class Default {
int height;
int width;
String url;
Default({this.height, this.width, this.url});
Map<String, dynamic> toJson() => {
'height': height,
'width': width,
'url': url,
};
factory Default.fromJSON(Map<String, dynamic> defaultJson) {
return Default(
height: defaultJson['height'],
width: defaultJson['width'],
url: defaultJson['url'],
);
}
}
class Thumbnails {
Default mDefault;
Medium medium;
High high;
Thumbnails({this.mDefault, this.medium, this.high});
var data = JsonEncoder().convert("");
Map<String, dynamic> toJson() => {
'default': mDefault,
'medium': medium,
'high': high,
};
factory Thumbnails.fromJSON(Map<String, dynamic> ThumbnailsJson) {
return Thumbnails(
mDefault: Default.fromJSON(ThumbnailsJson['default']),
medium: Medium.fromJSON(ThumbnailsJson['medium']),
high: High.fromJSON(ThumbnailsJson['high']),
);
}
}
现在我们已经描述了 JSON 的格式,我们希望它很容易解析:
YoutubeResponse parsedResponse =
YoutubeResponse.fromJSON(json.decode(response.body));
然后您可以通过 parsedResponse.items
访问项目,然后遍历它们并获取标题、描述等。
为了进一步回答 @Shaddy(有效),这里是我的 class 调用网络
class VideoNetwork {
static Future<List<Item>> fetchPost() async {
final response =
await http.get(<URL for Youtube Videos>);
if (response.statusCode == 200) {
// If the call to the server was successful, parse the JSON
return compute(parseVideos, response.body);
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load post');
}
}
static List<Item> parseVideos(String responseBody) {
YoutubeResponse response =
YoutubeResponse.fromJSON(json.decode(responseBody));
return response.items.toList();
}
}
回答问题可能有点晚了。
如果您想学习如何解析复杂的 json 数据,请使用这些文章。
1. Parsing JSON in the background - Flutter cookbook
和 Parsing complex JSON in Flutter- medium.com by Pooja Bhaumik
仅供参考 - Youtube 数据 api 响应采用嵌套 Json 格式
我使用了另一种方法,不推荐用于大型应用程序(我只是在玩弄它,这仅适用于上述类型 json 树)
使用异步函数检索和解析 Json 数据。
final String dataUrl = "YOUR-JSON-URL";
Future<String> getdet() async {
var response = await http.get(Uri.encodeFull(dataUrl), headers: {"Accept": "application/json"});
if (response.statusCode == 200) {
var responseBody = json.decode(response.body);
convertedData = responseBody["items"];
} else {
throw Exception('Failed to Load Data');
}
"items " is the array starting point
之后您可以在 Widget 中使用它
Widget recentWidget(){
return ListView.builder(
itemCount: convertedData == null ? 0 : recent.length,
itemBuilder: (BuildContext context, int index, ) {
return Column(
children: <Widget>[
Card(
child: Column(
children: <Widget>[
new Image.network(recent[index]["snippet"]["thumbnails"]["medium"]["url"]),
new ListTile(
title: new Text(recent[index]["snippet"]["title"]),
subtitle: Text("This is Subtitle"),
},
dense: true,
),
],
),
)
],
);
},shrinkWrap: true,
physics: ClampingScrollPhysics(),
)
}
希望对您有所帮助。
@Sh1d0w 的回答很好,但是对于那些不太了解 flutter 本身的人来说,它缺乏内容。我不得不说谢谢 Sh1d0w 因为你给了我创建我自己的代码来使用 youtube url.
获取数据的基础我使用 SearchDelegate 结合了他的答案以显示结果,我仍然需要添加代码来显示 next/previous 页面结果(youtube 按页面只给出 50 个结果)但要回答你的问题这是代码。 最后你将能够得到以下内容:
发布时间 频道编号 标题 描述 缩略图(默认、高、中) 直播内容 videoId
如果您想要 json 中的任何其他数据,您需要在 Class 代码段中添加您想要的内容,以便稍后返回和使用。 由于我使用的是在 snapshot.data object 中提供数据的 futureBuilder,您将需要使用这种声明来获取每个 属性:
snapshot.data[index].title
snapshot.data[index].description
snapshot.data[index].thumbnails.high.url
** 在代码中,您会看到 appTheme.text 等内容,它们只是颜色变量,请根据您的颜色更改它们。 ** 像 sizeBuild() 或 fontSizeBuild() 这样的函数是我为我的案例创建的函数,你只需要删除这些行并根据你的需要写任何数字
common_youtubeApi.dart
import 'dart:convert';
class YoutubeResponse{
//!-1st level parameters of youtube api for playlist
//!-https://developers.google.com/youtube/v3/docs/playlistItems/list#response
String kind;
String etag;
String nextPageToken;
String prevPageToken;
String regionCode;
List<Item> items;
//change the default values for the obtained values from url
YoutubeResponse({
this.kind,
this.etag,
this.nextPageToken,
this.prevPageToken,
this.regionCode,
this.items
});
//Json decode and make a dart object called Map
Map<String, dynamic> toJson() => {
'kind': kind,
'etag': etag,
'nextPageToken': nextPageToken,
'prevPageToken': prevPageToken,
'regionCode': regionCode,
'items': items,
};
factory YoutubeResponse.fromJSON(Map<String, dynamic> YoutubeResponseJson){
var list = YoutubeResponseJson['items'] as List;
List<Item> itemsList = list.map((i)=> Item.fromJSON(i)).toList();
return new YoutubeResponse(
kind: YoutubeResponseJson['kind'],
etag: YoutubeResponseJson['etag'],
nextPageToken: YoutubeResponseJson['nextPageToken'],
prevPageToken: YoutubeResponseJson['prevPageToken'],
regionCode: YoutubeResponseJson['regionCode'],
// mPageInfo: pageInfo.fromJSON(YoutubeResponseJson['pageInfo']),
items: itemsList
);
}
}
//---------Create an single video item
class Item{
String kind;
String etag;
String id;
Snippet snippet;
Item({
this.kind, this.etag, this.id, this.snippet
});
Map<String, dynamic> toJson() => {
'kind': kind,
'etag': etag,
'id': id,
'snippet': snippet,
};
factory Item.fromJSON(Map<String, dynamic> ItemJson) {
return Item(
kind: ItemJson['kind'],
etag: ItemJson['etag'],
id: ItemJson['id'],
snippet: Snippet.fromJSON(ItemJson['snippet']),
);
}
}
class Snippet {
String publishedAt;
String channelId;
String title;
String description;
Thumbnails thumbnails;
String channelTitle;
String liveBroadcastContent;
String videoId;
Snippet(
{this.publishedAt,
this.channelId,
this.title,
this.description,
this.thumbnails,
this.channelTitle,
this.liveBroadcastContent,
this.videoId,
});
Map<String, dynamic> toJson() => {
'publishedAt': publishedAt,
'channelId': channelId,
'title': title,
'description': description,
'thumbnails': thumbnails,
'channelTitle': channelTitle,
'liveBroadcastContent': liveBroadcastContent,
'videoId': videoId,
};
factory Snippet.fromJSON(Map<String, dynamic> snippetJson) {
return Snippet(
publishedAt: snippetJson['publishedAt'],
channelId: snippetJson['channelId'],
title: snippetJson['title'],
description: snippetJson['description'],
thumbnails: Thumbnails.fromJSON(snippetJson['thumbnails']) ,
channelTitle: snippetJson['channelTitle'],
liveBroadcastContent: snippetJson['liveBroadcastContent'],
videoId: snippetJson['resourceId']['videoId'],
);
}
}
class Medium {
int height;
int width;
String url;
Medium({this.height, this.width, this.url});
Map<String, dynamic> toJson() => {
'height': height,
'width': width,
'url': url,
};
factory Medium.fromJSON(Map<String, dynamic> MediumJson) {
return Medium(
height: MediumJson['height'],
width: MediumJson['width'],
url: MediumJson['url'],
);
}
}
class High {
int height;
int width;
String url;
High({this.height, this.width, this.url});
Map<String, dynamic> toJson() => {
'height': height,
'width': width,
'url': url,
};
factory High.fromJSON(Map<String, dynamic> HighJson) {
return High(
height: HighJson['height'],
width: HighJson['width'],
url: HighJson['url'],
);
}
}
class Default {
int height;
int width;
String url;
Default({this.height, this.width, this.url});
Map<String, dynamic> toJson() => {
'height': height,
'width': width,
'url': url,
};
factory Default.fromJSON(Map<String, dynamic> defaultJson) {
return Default(
height: defaultJson['height'],
width: defaultJson['width'],
url: defaultJson['url'],
);
}
}
class Thumbnails {
Default mDefault;
Medium medium;
High high;
Thumbnails({this.mDefault, this.medium, this.high});
var data = JsonEncoder().convert("");
Map<String, dynamic> toJson() => {
'default': mDefault,
'medium': medium,
'high': high,
};
factory Thumbnails.fromJSON(Map<String, dynamic> thumbnailsJson) {
return Thumbnails(
mDefault: Default.fromJSON(thumbnailsJson['default']),
medium: Medium.fromJSON(thumbnailsJson['medium']),
high: High.fromJSON(thumbnailsJson['high']),
);
}
}
searchList.dart 要导入的文件:
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:denApp/keys/app_keys.dart'; //put your keys always in a different file with gitignore
import 'package:url_launcher/url_launcher.dart'; //for links in each item
import 'package:denApp/Widgets/common_youtubeAPI.dart'; //here are the models of each data requested in the json to be used here
class DataSearch extends SearchDelegate<List>{
var nextPageToken;
var prevPageToken;
Future<void> _launched;
//ajax/http request for data
Future<List<Snippet>> getVideos(http.Client client) async {
YoutubeResponse parsedResponse;
final response = await client
.get('https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId=$youtubeTrickPlaylist&key=$youtubeAPIKEY&maxResults=50');
List<Snippet> videos = [];
parsedResponse = YoutubeResponse.fromJSON(json.decode(response.body));
for(var i=0; i < parsedResponse.items.length; i++ ){
videos.add(parsedResponse.items[i].snippet);
}
this.nextPageToken = parsedResponse.nextPageToken;
this.prevPageToken = parsedResponse.prevPageToken;
print(this.nextPageToken);
print(this.prevPageToken);
return videos;
}
//We use the launcher plugin to manage the click to go to the website, please visit the plugin web for info how to use it
Future<void> _launchInBrowser(String url) async {
print(url);
if (await canLaunch(url)) {
await launch(
url,
forceSafariVC: false,
forceWebView: false,
headers: <String, String>{'my_header_key': 'my_header_value'},
);
} else {
throw 'Could not launch $url';
}
}
//------------------------------------------------------
//--This part is to edit the colors and design of the searchDelegate widget, I have a separated file with the themes, colors which I call using appTheme, so you need to change all those variables with your own colors.
@override
ThemeData appBarTheme(BuildContext context) {
return ThemeData(
primaryColor: appTheme.text,
backgroundColor: appTheme.dark,
bottomAppBarColor: appTheme.dark,
canvasColor: appTheme.dark,
);
}
//-------------------------------------------------------------
//---Here we define how it will works the SearchDelegate and its icons/functions-----------
@override
List<Widget> buildActions(BuildContext context) {
// ----This is the icon which will delete whatever you write in your searchbar
return [IconButton(icon: Icon(Icons.clear), onPressed: () {
query="";
},)];
}
@override
Widget buildLeading(BuildContext context) {
// ---- This is the icon which will allow you to close the search delegate going to the back page.
return IconButton(icon: Icon(Icons.arrow_back), onPressed: (){
close(context, null);
});
}
@override
Widget buildResults(BuildContext context) {
// nothing here
throw UnimplementedError();
}
@override
Widget buildSuggestions(BuildContext context) {
// This is actually the place where you will do all the work when you receive the data from your future request. Since we are going to build something after we wait for the data, we need to use futureBuilder instead of builder only. In the future propiety you will put the function which will do the request to the api
return FutureBuilder<List<Snippet>>(
future: getVideos(http.Client()),
builder: (context, snapshot) {
//during the build you need to check the connection state so depending on what is happening you will show something , like a loader if it is waiting state, etc
switch (snapshot.connectionState){
case ConnectionState.none:
return Text('none', style: TextStyle(color:Colors.black));
case ConnectionState.active:
return Text('active', style: TextStyle(color:Colors.black));
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
default:
//after checking connection we need to check if we do not got null data from the request
if(snapshot.data != null){
var dt = snapshot.data.where((p)=> p.title.toLowerCase().contains(query.toLowerCase())).toList();
return dt.isEmpty? Center(child:Text("There was no results for your query", style: TextStyle(color: appTheme.text))) : ListView.builder(
itemCount: dt.length,
padding: EdgeInsets.zero,
itemBuilder: (context, index) {
if(query.isEmpty){
return Card(
margin: (index == (dt.length - 1)) ? EdgeInsets.only(top: sizeBuild(context, "height", 5), bottom: sizeBuild(context, "height", 15)) : EdgeInsets.only(top: sizeBuild(context, "height", 5)),
color: appTheme.bgLight.withOpacity(.1),
child: InkWell(
splashColor: appTheme.linkB.withAlpha(30),
onTap: () {
_launched = _launchInBrowser('https://www.youtube.com/watch?v=${snapshot.data[index].videoId}');
},
child: Container(
child: Row(
children: <Widget>[
Container(
width:sizeBuild(context, "width", 140),
child: Image.network("${snapshot.data[index].thumbnails.high.url}", fit: BoxFit.cover),
),
Container(
// color: Colors.red,
margin: EdgeInsets.symmetric(horizontal: sizeBuild(context, "width",10)),
width:sizeBuild(context, "width", 280),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(snapshot.data[index].title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle (
color: appTheme.text,
fontWeight: FontWeight.bold,
fontSize: fontSizeBuild(context, 'body')
),
),
SizedBox(
height: sizeBuild(context, "height", 5),
),
Text(snapshot.data[index].description,
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: TextStyle (
color: appTheme.text.withOpacity(.7),
fontSize: fontSizeBuild(context, 'small')
),
),
],
),
),
],
),
),
),
);
}else{
return Card(
margin: (index == (dt.length - 1)) ? EdgeInsets.only(top: sizeBuild(context, "height", 5), bottom: sizeBuild(context, "height", 15)) : EdgeInsets.only(top: sizeBuild(context, "height", 5)),
color: appTheme.bgLight.withOpacity(.1),
child: InkWell(
splashColor: appTheme.linkB.withAlpha(30),
onTap: () {
_launched = _launchInBrowser('https://www.youtube.com/watch?v=${snapshot.data[index].videoId}');
},
child: Container(
child: Row(
children: <Widget>[
Container(
width:sizeBuild(context, "width", 140),
child: Image.network("${snapshot.data[index].thumbnails.high.url}", fit: BoxFit.cover),
),
Container(
margin: EdgeInsets.symmetric(horizontal: sizeBuild(context, "width",10)),
width:sizeBuild(context, "width", 280),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(snapshot.data[index].title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle (
color: appTheme.text,
fontWeight: FontWeight.bold,
fontSize: fontSizeBuild(context, 'body')
),
),
SizedBox(
height: sizeBuild(context, "height", 20),
),
Text(snapshot.data[index].description,
maxLines: 5,
overflow: TextOverflow.ellipsis,
style: TextStyle (
color: appTheme.text.withOpacity(.7),
fontSize: fontSizeBuild(context, 'small')
),
),
],
),
),
],
),
),
),
);
}
},
);
}else{
//this is the widget to give if there is no data
return Center(child: Text("There was no data from the server", style: TextStyle(color:appTheme.text)));
}
}
},
);
}
}
你的代码片段中的问题比其他答案所暗示的要简单得多。
首先,您的 JSON 字符串可能有误,您漏掉了几件事。您可能还忘记了对 etag 值进行转义:"etag": "\\"XI7nbFXulYBIpL0ayR_gDh3eu1k/s7-xmHXpuqQxYzDp_wxhm59K4LE\\"",
。当然,所有这些都可能只是复制错误。
你 可以 创建额外的 类,如果你想长期维护你的代码,你绝对应该这样做。有代码生成器可以为您完成大部分工作。但是,您 不需要 创建那些巨大的 类,只是为了得到 JSON 的 3 个值。你也不需要在不同的隔离上旋转,JSON 很小(对于计算机),你不会注意到解析它的任何性能问题。
你采取的方法没有问题。这是一个简单有效的解决方案,非常适合非常简单的脚本和应用程序。
唯一的问题是你忘记了items
是一个列表,所以你需要先取那个列表中的第0个元素。修复之后,一切都按预期工作:parsed['items'][0]['snippet']['title']
.
您可以将此复制到 dartpad.dev 并自己尝试。
import 'dart:convert';
void main() {
final parsed = jsonDecode(responseBody);
String title = parsed['items'][0]['snippet']['title'];
String description = parsed['items'][0]['snippet']['description'];
String thumbnail = parsed['items'][0]['snippet']['thumbnails']['default']['url'];
print('title: $title');
print('description: $description');
print('thumbnail: $thumbnail');
}
const responseBody = '''{
"kind": "youtube#videoListResponse",
"etag": "\\"XI7nbFXulYBIpL0ayR_gDh3eu1k/s7-xmHXpuqQxYzDp_wxhm59K4LE\\"",
"pageInfo": {
"totalResults": 1,
"resultsPerPage": 1
},
"items": [
{
"kind": "youtube#video",
"etag": "\\"XI7nbFXulYBIpL0ayR_gDh3eu1k/pajQ7iBy-7A0V_owifxkw-Kbw-Y\\"",
"id": "7lCDEYXw3mM",
"snippet": {
"publishedAt": "2012-06-20T23:12:38.000Z",
"channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw",
"title": "Google I/O 101: Q&A On Using Google APIs",
"description": "Antonio Fuentes speaks to us and takes questions on working with Google APIs and OAuth 2.0.",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/7lCDEYXw3mM/default.jpg",
"width": 120,
"height": 90
},
"medium": {
"url": "https://i.ytimg.com/vi/7lCDEYXw3mM/mqdefault.jpg",
"width": 320,
"height": 180
},
"high": {
"url": "https://i.ytimg.com/vi/7lCDEYXw3mM/hqdefault.jpg",
"width": 480,
"height": 360
}
}
}
}
]
}''';