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

Flutter/Dart解析API响应时处理空值以避免类型不匹配错误的正确方法

Flutter/Dart解析API响应时处理空值以避免类型不匹配错误的正确方法

看起来你遇到了Dart空安全最典型的坑之一——API返回的null值和你模型里定义的非空类型撞车了。别慌,咱们一步步把这个问题解决掉,以后再遇到类似情况就得心应手了。

核心问题分析

你的API里有些字段(比如match_day、scheduled_at、ended_at)会返回null,但如果你的模型类里把这些字段定义成非空类型(比如int matchDay而不是int? matchDay),Dart的空安全机制就会直接抛出类型不匹配的错误,这就是你现在看到的TypeError: null: type 'Null' is not a subtype of type 'String'的原因。

解决方案分三步走

1. 给模型类的可空字段正确标记可空类型

首先,你要把API可能返回null的字段全部标记为可空类型(在类型后面加?)。结合你的API响应,我帮你修正了Match模型类,你可以参考:

import 'package:wasco_games_mobile_app_v1/models/organization.dart';
import 'package:wasco_games_mobile_app_v1/models/tournament.dart';

class Match {
  final int id;
  final String? matchCode;
  final Team homeTeam;
  final Team awayTeam;
  final int homeScore;
  final int awayScore;
  final String status;
  final String? sportCategory;
  final String? venue;
  final String? round;
  final int? matchDay; // API返回null,标记为可空
  final Tournament tournament;
  final DateTime? scheduledAt; // API返回null,标记为可空
  final DateTime? startedAt;
  final DateTime? endedAt; // API返回null,标记为可空
  final LiveScore? liveData;

  Match({
    required this.id,
    this.matchCode,
    required this.homeTeam,
    required this.awayTeam,
    required this.homeScore,
    required this.awayScore,
    required this.status,
    this.sportCategory,
    this.venue,
    this.round,
    this.matchDay,
    required this.tournament,
    this.scheduledAt,
    this.startedAt,
    this.endedAt,
    this.liveData,
  });

  // 关键:实现安全的fromJson解析方法
  factory Match.fromJson(Map<String, dynamic> json) {
    return Match(
      id: json['id'] as int,
      matchCode: json['match_code'] as String?,
      homeTeam: Team.fromJson(json['home_team'] as Map<String, dynamic>),
      awayTeam: Team.fromJson(json['away_team'] as Map<String, dynamic>),
      // 处理字符串转int,同时防null(API里home_score是字符串,比如"1")
      homeScore: int.tryParse(json['home_score'] as String) ?? 0,
      awayScore: int.tryParse(json['away_score'] as String) ?? 0,
      status: json['status'] as String,
      sportCategory: json['sport_category'] as String?,
      venue: json['venue'] as String?,
      round: json['round'] as String?,
      matchDay: json['match_day'] as int?, // 直接接受null
      tournament: Tournament.fromJson(json['tournament'] as Map<String, dynamic>),
      // 解析DateTime时先判断是否为null
      scheduledAt: json['scheduled_at'] != null 
          ? DateTime.parse(json['scheduled_at'] as String) 
          : null,
      startedAt: json['started_at'] != null 
          ? DateTime.parse(json['started_at'] as String) 
          : null,
      endedAt: json['ended_at'] != null 
          ? DateTime.parse(json['ended_at'] as String) 
          : null,
      liveData: json['live_data'] != null 
          ? LiveScore.fromJson(json['live_data'] as Map<String, dynamic>) 
          : null,
    );
  }
}

// 配套的Team模型示例(也要处理可空字段)
class Team {
  final int id;
  final String name;
  final String shortName;
  final Organization organization;

  Team({
    required this.id,
    required this.name,
    required this.shortName,
    required this.organization,
  });

  factory Team.fromJson(Map<String, dynamic> json) {
    return Team(
      id: json['id'] as int,
      name: json['name'] as String,
      shortName: json['short_name'] as String,
      organization: Organization.fromJson(json['organization'] as Map<String, dynamic>),
    );
  }
}

// LiveScore模型示例
class LiveScore {
  final String elapsedTime;
  final String matchStatus;
  final Map<String, String> currentScore;
  final DateTime lastUpdated;

  LiveScore({
    required this.elapsedTime,
    required this.matchStatus,
    required this.currentScore,
    required this.lastUpdated,
  });

  factory LiveScore.fromJson(Map<String, dynamic> json) {
    return LiveScore(
      elapsedTime: json['elapsed_time'] as String,
      matchStatus: json['match_status'] as String,
      currentScore: Map<String, String>.from(json['current_score'] as Map),
      lastUpdated: DateTime.parse(json['last_updated'] as String),
    );
  }
}

2. 解析时用默认值替代null(可选,根据业务需求)

如果你不想在UI层处理可空值,可以在解析阶段就给null字段设置默认值。比如:

  • matchDay: json['match_day'] as int? ?? 0 // 把null替换成0
  • venue: json['venue'] as String? ?? '未指定' // 把null替换成默认文本
  • 注意:DateTime类型不能随便给默认值,除非你有合理的业务逻辑(比如用当前时间替代,但通常不建议),所以还是保持可空更稳妥。

3. UI层安全处理可空值

在UI展示的时候,一定要避免直接使用可空字段,否则还是会报错。你可以用??操作符给null设置替代文本:

// 示例:显示matchDay
Text(match.matchDay?.toString() ?? '无轮次'),
// 示例:显示scheduledAt
Text(match.scheduledAt?.toLocal().toString() ?? '未安排时间'),

额外的小建议

  • 尽量不要在代码里用!强制非空(比如你的service里的response.data!),除非你100%确定这个值不可能为null。虽然你已经判断了response.data != null,但如果API逻辑变了,这个!就会变成隐患。
  • 可以考虑用json_serializable包来自动生成fromJson/toJson方法,它会帮你自动处理空安全,减少手动写代码的错误。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 07:19:35