【问题标题】:Decode Json from Youtube api on Flutter在 Flutter 上从 Youtube api 解码 Json
【发布时间】:2019-04-14 15:26:10
【问题描述】:

我调用了 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 youtube-data-api


    【解决方案1】:

    您正在尝试的内容不适用于 Dart,这不是 javascript。 Dart 有非常强大的类型系统,这很棒。您正在尝试将值分配给您定义为String 的变量,但您定义的响应是动态的,因此 Dart 无法验证值分配。还有items是数组,没有这个key items->sn-p.

    这样做的正确方法是创建模型定义,它将处理反序列化并提供访问您感兴趣的属性的便捷方式。

    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 访问这些项目,然后循环访问它们并获取标题、描述等。

    【讨论】:

    • 我添加了你的代码,但它给了我一个 [VERBOSE-2:shell.cc(188)] Dart 错误:未处理的异常:NoSuchMethodError:方法 'fromJSON' 在 null 上被调用。
    • @CapitanLuzzatto 您创建并导入了所有模型吗?
    • 是的,我还注意到 Android Studio 无法识别 Item 对象上的字段 Id 和 YouTubeResponse 对象上的 mPageInfo。
    【解决方案2】:

    为了进一步回答@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();
      }
    }
    

    【讨论】:

      【解决方案3】:

      回答这个问题可能有点晚了。

      如果您想学习如何解析复杂的 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”是数组的起点

      之后,您可以在 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(),
            )
          }
      

      希望这会有所帮助。

      【讨论】:

        【解决方案4】:

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

        我使用 SearchDelegate 结合了他的答案以显示结果,我仍然需要添加代码以显示下一个/上一个页面结果(youtube 仅按页面提供 50 个结果)但要回答您的问题,这里是代码. 最后,您将能够获得以下信息:

        发布时间 频道 ID 标题 描述 缩略图(默认、高、中) 直播内容 视频ID

        如果您想要 json 中的任何其他数据,您需要在 Class Snippet 中添加您想要的内容,以便稍后返回和使用。 由于我使用的是在 snapshot.data 对象中提供数据的 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)));
                    }
                }
              },
            );
          }
        
        }
        

        【讨论】:

          【解决方案5】:

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

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

          【讨论】:

            猜你喜欢
            • 2012-03-22
            • 2011-11-30
            • 2011-05-15
            • 2021-11-20
            • 2015-07-21
            • 1970-01-01
            • 2022-01-18
            • 2023-03-08
            • 2021-04-09
            相关资源
            最近更新 更多