You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

错误原因

  1. 核心错误:parseMovies函数中,jsonDecode(responseBody)返回的是包含results数组的顶层Map,直接调用cast<Map<String, dynamic>>()尝试将整个Map转为数组类型,导致类型转换失败。
  2. 次要问题:
    • 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 10:27:24