Flutter解析TMDB API JSON时遭遇NoSuchMethodError: 'cast'错误求助
解决Flutter解析TMDB API JSON时的NoSuchMethodError: 'cast'错误
问题场景
通过The Movie Database(TMDB)的REST API获取热门电影列表,解析JSON数据展示电影列表时,触发NoSuchMethodError: 'cast'错误。API返回的JSON结构包含results字段,对应电影列表数组。
原代码
app.dart
import 'dart:convert'; import 'package:flutter/material.dart'; import 'models/movie_response.dart'; import 'package:auth_request/api_key.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; Future<List<Movie>> fetchMovies(http.Client client) async { final response = await client .get(Uri.parse('https://api.themoviedb.org/3/trending/movie/week?api_key=$apiKey')); // 使用compute在独立隔离区解析 return compute(parseMovies, response.body); } // 将响应体转为List<Movie>的函数 List<Movie> parseMovies(String responseBody) { final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>(); return parsed.map<Movie>((json) => Movie.fromJson(json)).toList(); } class MovieApp extends StatefulWidget { const MovieApp({super.key}); @override MovieAppState createState() => MovieAppState(); } class MovieAppState extends State<MovieApp> { late Future<Movie> futureAlbum; @override void initState() { super.initState(); } @override Widget build(BuildContext context) { return MaterialApp( title: 'MovieDB List', theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( appBar: AppBar( title: const Text('MovieDB List'), ), body: Center( child: FutureBuilder<List<Movie>>( future: fetchMovies(http.Client()), builder: (context, snapshot) { if (snapshot.hasError) { return Text('${snapshot.error}'); } else if (snapshot.hasData) { return MoviesList(movies: snapshot.data!); } else { return const Center( child: CircularProgressIndicator(), ); } }, ), ), ), ); } } class MoviesList extends StatelessWidget { const MoviesList({super.key, required this.movies}); final List<Movie> movies; @override Widget build(BuildContext context) { return GridView.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, ), itemBuilder: (context, index) { return Image.network(movies[index].original_title); }, ); } }
movie.dart
class Movie { final String original_title; final String overview; final String poster_path; final String release_date; const Movie({ required this.original_title, required this.overview, required this.poster_path, required this.release_date, }); factory Movie.fromJson(Map<String, dynamic> json) { return Movie( original_title: json['original_title'], overview: json['overview'], poster_path: json['poster_path'], release_date: json['release_date'], ); } }
错误原因
- 核心错误:
parseMovies函数中,jsonDecode(responseBody)返回的是包含results数组的顶层Map,直接调用cast<Map<String, dynamic>>()尝试将整个Map转为数组类型,导致类型转换失败。 - 次要问题:
MovieAppState中声明的futureAlbum变量类型错误且未使用,属于冗余代码。MoviesList中错误地将电影标题(original_title)作为Image.network的URL,应该使用海报路径拼接TMDB图片地址。Movie.fromJson未处理字段为空的情况,可能触发空值类型错误。
修复方案
1. 修正JSON解析逻辑
修改parseMovies函数,先提取顶层Map中的results数组,再转换为List<Movie>:
List<Movie> parseMovies(String responseBody) { final Map<String, dynamic> responseMap = jsonDecode(responseBody); final List<dynamic> movieResults = responseMap['results']; return movieResults.map<Movie>((json) => Movie.fromJson(json)).toList(); }
2. 清理冗余变量
删除MovieAppState中的late Future<Movie> futureAlbum;,因为该变量未被使用。
3. 修正图片加载逻辑
修改MoviesList的itemBuilder,使用正确的海报URL并添加错误处理:
itemBuilder: (context, index) { final posterUrl = 'https://image.tmdb.org/t/p/w500/${movies[index].poster_path}'; return Image.network( posterUrl, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return const Center(child: Text('加载失败')); }, ); },
4. 处理空值字段
更新Movie.fromJson,为空字段设置默认值:
factory Movie.fromJson(Map<String, dynamic> json) { return Movie( original_title: json['original_title'] ?? '未知标题', overview: json['overview'] ?? '暂无简介', poster_path: json['poster_path'] ?? '', release_date: json['release_date'] ?? '未知日期', ); }
修复后完整代码
app.dart
import 'dart:convert'; import 'package:flutter/material.dart'; import 'models/movie.dart'; // 注意路径需匹配你的文件结构 import 'package:auth_request/api_key.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; Future<List<Movie>> fetchMovies(http.Client client) async { final response = await client .get(Uri.parse('https://api.themoviedb.org/3/trending/movie/week?api_key=$apiKey')); return compute(parseMovies, response.body); } List<Movie> parseMovies(String responseBody) { final Map<String, dynamic> responseMap = jsonDecode(responseBody); final List<dynamic> movieResults = responseMap['results']; return movieResults.map<Movie>((json) => Movie.fromJson(json)).toList(); } class MovieApp extends StatefulWidget { const MovieApp({super.key}); @override MovieAppState createState() => MovieAppState(); } class MovieAppState extends State<MovieApp> { @override void initState() { super.initState(); } @override Widget build(BuildContext context) { return MaterialApp( title: 'MovieDB List', theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( appBar: AppBar( title: const Text('MovieDB List'), ), body: FutureBuilder<List<Movie>>( future: fetchMovies(http.Client()), builder: (context, snapshot) { if (snapshot.hasError) { return Center(child: Text('错误: ${snapshot.error}')); } else if (snapshot.hasData) { return MoviesList(movies: snapshot.data!); } else { return const Center(child: CircularProgressIndicator()); } }, ), ), ); } } class MoviesList extends StatelessWidget { const MoviesList({super.key, required this.movies}); final List<Movie> movies; @override Widget build(BuildContext context) { return GridView.builder( padding: const EdgeInsets.all(8), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 8, mainAxisSpacing: 8, ), itemCount: movies.length, // 添加itemCount避免无限加载 itemBuilder: (context, index) { final posterUrl = 'https://image.tmdb.org/t/p/w500/${movies[index].poster_path}'; return Image.network( posterUrl, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return const Center(child: Text('加载失败')); }, ); }, ); } }
movie.dart
class Movie { final String original_title; final String overview; final String poster_path; final String release_date; const Movie({ required this.original_title, required this.overview, required this.poster_path, required this.release_date, }); factory Movie.fromJson(Map<String, dynamic> json) { return Movie( original_title: json['original_title'] ?? '未知标题', overview: json['overview'] ?? '暂无简介', poster_path: json['poster_path'] ?? '', release_date: json['release_date'] ?? '未知日期', ); } }
内容的提问来源于stack exchange,提问作者shotmeinthehead
相关产品推荐
相关产品推荐

