在 Flutter 上从 Youtube api 解码 Json

问题描述 投票:0回答:5

我调用 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 个节点:

  1. 标题
  2. 描述
  3. 默认缩略图的网址

确实,我得到了 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);
json flutter dart youtube-data-api
5个回答
2
投票

您正在尝试的内容不适用于 Dart,这不是 javascript。 Dart 有非常强大的类型系统,这很棒。您尝试为定义为

String
的变量赋值,但您定义了动态响应,因此 Dart 无法验证值分配。而且 items 是数组,没有这样的关键 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
访问这些项目,然后循环浏览它们并获取标题、描述等。


1
投票

为了进一步回答@Shaddy(有效),这里是我调用网络的类

    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();
  }
}

0
投票

回答问题可能有点晚了。

如果您想学习如何解析复杂的 json 数据,请使用这些文章。

1。后台解析 JSON - Flutter Cookbook

在 Flutter-medium.com 中解析复杂的 JSON,作者: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”是数组起点

之后您可以在小部件中使用它

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(),
      )
    }

希望这有帮助。


0
投票

@Sh1d0w 的答案很好,但对于那些不太了解 flutter 本身的人来说,它缺乏内容。我不得不说谢谢 Sh1d0w,因为你为我提供了创建自己的代码以使用 youtube url 获取数据的基础。

我使用 SearchDelegate 组合了他的答案以显示结果,我仍然需要添加代码来显示下一页/上一页结果(youtube 仅按页给出 50 个结果),但这里是代码来回答您的问题。 最后你将能够得到以下内容:

发表于 频道号 标题 描述 缩略图(默认、高、中) 直播内容 视频ID

如果您想要 json 中的任何其他数据,则需要在类片段中添加您想要的内容,以便稍后返回和使用。 由于我使用的是 futureBuilder,它在 snapshot.data 对象中提供数据,因此您将需要使用这种声明来获取每个属性:

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)));
            }
        }
      },
    );
  }

}

0
投票

您的代码片段中的问题比其他答案所暗示的要简单得多。

首先,你的 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 } } } } ] }''';

© www.soinside.com 2019 - 2024. All rights reserved.