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

Flutter中如何正确在函数上使用json_serializable?

使用json_serializable解析TheCocktailDB API时Ingredients列表为null的解决方法

问题背景

想要将Flutter应用中手动实现的CocktailModel改为用json_serializable优化序列化逻辑,对接TheCocktailDB API时,自定义的_getIngredients函数无法正确生成配料列表,导致ingredientsList解析为null。

尝试的json_serializable版本代码:

import 'package:random_cocktail_app/domain/models/ingredient_model.dart';
import 'package:json_annotation/json_annotation.dart';

part 'cocktail_model.g.dart';

@JsonSerializable()
class CocktailModel {
  @JsonKey(name: 'strDrink')
  final String name;

  @JsonKey(name: 'strCategory')
  final String category;

  @JsonKey(name: 'strAlcoholic')
  final String alcoholic;

  @JsonKey(name: 'strGlass')
  final String glassType;

  @JsonKey(name: 'strDrinkThumb')
  final String pictureUrl;

  @JsonKey(name: 'strInstructions')
  final String instructions;


  @JsonKey(fromJson: _getIngredients)
  final List<IngredientModel> ingredientsList;

  CocktailModel({
    required this.name,
    required this.category,
    required this.alcoholic,
    required this.glassType,
    required this.pictureUrl,
    required this.instructions,
    required this.ingredientsList,
  });

  factory CocktailModel.fromJson(Map<String, dynamic> json) =>
      _$CocktailModelFromJson(json);

  Map<String, dynamic> toJson() => _$CocktailModelToJson(this);

  static List<IngredientModel> _getIngredients(
    Map<String, dynamic> json,
  ) {
    List<IngredientModel> ingredientsList = [];

    for (var i = 1; i <= 16; i++) {
      if (json['strIngredient$i'] == null) {
        continue;
      }
      if (json['strMeasure$i'] == null) {
        continue;
      }
      ingredientsList.add(
        IngredientModel(
          name: json['strIngredient$i'],
          mesure: json['strMeasure$i'],
        ),
      );
    }
    return ingredientsList;
  }
}

原手动实现代码:

class CocktailModel {
  final String name;
  final String category;
  final String alcoholic;
  final String glassType;
  final String pictureUrl;
  final String instructions;
  final List<IngredientModel> ingredientsList;

  CocktailModel({
    required this.name,
    required this.category,
    required this.alcoholic,
    required this.glassType,
    required this.pictureUrl,
    required this.instructions,
    required this.ingredientsList,
  });

  CocktailModel.fromJson(Map<String, dynamic> json)
      : name = json['strDrink'] ?? '',
        category = json['strCategory'] ?? '',
        alcoholic = json['strAlcoholic'] ?? '',
        glassType = json['strGlass'] ?? '',
        pictureUrl = json['strDrinkThumb'] ?? '',
        instructions = json['strInstructions'] ?? '',
        ingredientsList = _getIngredients(json);

  static List<IngredientModel> _getIngredients(
    Map<String, dynamic> json,
  ) {
    List<IngredientModel> ingredientsList = [];

    for (var i = 1; i <= 16; i++) {
      if (json['strIngredient$i'] == null) {
        continue;
      }
      if (json['strMeasure$i'] == null) {
        continue;
      }
      ingredientsList.add(
        IngredientModel(
          name: json['strIngredient$i'],
          mesure: json['strMeasure$i'],
        ),
      );
    }
    return ingredientsList;
  }
}

问题原因

错误出在@JsonKey(fromJson: _getIngredients)的使用逻辑:

  • json_serializable会认为ingredientsList对应API返回中的一个同名字段,尝试读取该字段的值并传递给_getIngredients函数
  • 但TheCocktailDB API中并没有ingredientsList字段,配料信息分散在strIngredient1~16和strMeasure1~16这些顶级字段中
  • 因此传递给_getIngredients的参数是null,而函数期望接收完整的Map<String, dynamic>对象,导致解析失败,最终ingredientsList为null

修复方案

步骤1:标记ingredientsList为忽略字段

给ingredientsList添加@JsonKey(ignore: true),告知json_serializable该字段不需要从API的JSON字段中映射,而是手动处理。

步骤2:修改fromJson工厂方法

在工厂方法中,先调用生成的_$CocktailModelFromJson处理其他字段,再手动调用_getIngredients生成配料列表,最后构造完整的CocktailModel实例。

步骤3:添加空值默认处理(可选但推荐)

给其他字段添加defaultValue,避免API返回null时出现类型错误,和原手动实现的?? ''逻辑保持一致。

修复后的完整代码

import 'package:random_cocktail_app/domain/models/ingredient_model.dart';
import 'package:json_annotation/json_annotation.dart';

part 'cocktail_model.g.dart';

@JsonSerializable()
class CocktailModel {
  @JsonKey(name: 'strDrink', defaultValue: '')
  final String name;

  @JsonKey(name: 'strCategory', defaultValue: '')
  final String category;

  @JsonKey(name: 'strAlcoholic', defaultValue: '')
  final String alcoholic;

  @JsonKey(name: 'strGlass', defaultValue: '')
  final String glassType;

  @JsonKey(name: 'strDrinkThumb', defaultValue: '')
  final String pictureUrl;

  @JsonKey(name: 'strInstructions', defaultValue: '')
  final String instructions;

  // 标记为忽略字段,手动处理解析
  @JsonKey(ignore: true)
  final List<IngredientModel> ingredientsList;

  CocktailModel({
    required this.name,
    required this.category,
    required this.alcoholic,
    required this.glassType,
    required this.pictureUrl,
    required this.instructions,
    required this.ingredientsList,
  });

  factory CocktailModel.fromJson(Map<String, dynamic> json) {
    // 先处理其他字段
    final baseModel = _$CocktailModelFromJson(json);
    // 手动生成配料列表
    final ingredients = _getIngredients(json);
    // 构造完整实例
    return CocktailModel(
      name: baseModel.name,
      category: baseModel.category,
      alcoholic: baseModel.alcoholic,
      glassType: baseModel.glassType,
      pictureUrl: baseModel.pictureUrl,
      instructions: baseModel.instructions,
      ingredientsList: ingredients,
    );
  }

  Map<String, dynamic> toJson() => _$CocktailModelToJson(this);

  static List<IngredientModel> _getIngredients(Map<String, dynamic> json) {
    final ingredientsList = <IngredientModel>[];

    for (var i = 1; i <= 16; i++) {
      final ingredient = json['strIngredient$i'];
      final measure = json['strMeasure$i'];
      // 跳过空值,和原逻辑一致
      if (ingredient == null || measure == null) continue;
      ingredientsList.add(
        IngredientModel(
          name: ingredient,
          mesure: measure,
        ),
      );
    }
    return ingredientsList;
  }
}

验证生成代码

修改完成后,运行以下命令重新生成.g.dart文件:

flutter pub run build_runner build

生成的_$CocktailModelFromJson会自动处理除ingredientsList外的所有字段,而配料列表的逻辑由我们手动控制,和原手动实现的行为完全一致,同时保留了json_serializable的优化特性。

内容的提问来源于stack exchange,提问作者Ronix

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 16:40:15