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
相关产品推荐
相关产品推荐

