Flutter JsonSerializable UserModel toJson传null值问题咨询
对接获取用户数据的API时,编写UserModel类存储解码后的用户数据。修改部分用户信息(例如个人资料名称)并向API的用户资源接口发起POST请求时,UserModel的toJson()方法除已更新的字段外,会将其余用户字段以null值发送给API。
预期行为:
toJson()方法应当检测null值字段,将其替换为之前从API获取的原有数据,而非向API发送null值。
需要实现UserModel向API发送数据时保留原有用户字段的有效值。
当前相关代码如下:
序列化入口:
String userModelToJson(UserModel data) => jsonEncode(data);
UserModel类实现:
@JsonSerializable() class UserModel { UserModel({ this.type, this.bio, this.name, this.interests, this.presentation, this.links, this.location, this.school, this.occupation, this.createdAt, this.lastUpdatedAt, }); final String? type; final String? bio; final String? name; final List<dynamic>? interests; final Presentation? presentation; final Links? links; final Location? location; final School? school; final Occupation? occupation; final int? createdAt; final int? lastUpdatedAt; factory UserModel.fromJson(Map<String, dynamic> json) => _$UserModelFromJson(json); Map<String, dynamic> toJson() => _$UserModelToJson(this); }
json_serializable自动生成的序列化/反序列化代码:
UserModel _$UserModelFromJson(Map<String, dynamic> json) => UserModel( type: json['type'] as String?, bio: json['bio'] as String?, name: json['name'] as String?, interests: json['interests'] as List<dynamic>?, presentation: json['presentation'] == null ? null : Presentation.fromJson(json['presentation'] as Map<String, dynamic>), links: json['links'] == null ? null : Links.fromJson(json['links'] as Map<String, dynamic>), location: json['location'] == null ? null : Location.fromJson(json['location'] as Map<String, dynamic>), school: json['school'] == null ? null : School.fromJson(json['school'] as Map<String, dynamic>), occupation: json['occupation'] == null ? null : Occupation.fromJson(json['occupation'] as Map<String, dynamic>), createdAt: json['createdAt'] as int?, lastUpdatedAt: json['lastUpdatedAt'] as int?, ); Map<String, dynamic> _$UserModelToJson(UserModel instance) => <String, dynamic>{ 'type': instance.type, 'bio': instance.bio, 'name': instance.name, 'interests': instance.interests, 'presentation': instance.presentation, 'links': instance.links, 'location': instance.location, 'school': instance.school, 'occupation': instance.occupation, };
获取用户数据方法:
Future<UserModel> getUserData() async { UserModel? user; final token = await _storage.getKey("Token"); ApiService.client.options.headers['Authorization'] = 'Bearer $token'; final response = await ApiService.client.get(ApiEndpoint.profile); if (response.statusCode == 200 || response.statusCode == 201) { user = UserModel.fromJson(response.data); return user; } else { throw "Couldn't load user"; } }
核心原因是更新用户信息时,新建的UserModel实例仅赋值了修改的字段,其余未赋值的可空字段默认值为null,序列化时null会被直接带入请求体。以下两种实现方式均可解决问题,按需选择即可:
方案1:通过copyWith方法基于原有数据生成更新实例
该方案符合不可变数据模型的设计规范,不需要修改序列化逻辑,从数据生成源头避免null字段。
- 给
UserModel添加copyWith方法,支持基于现有实例覆盖指定字段,其余字段保留原有值:
@JsonSerializable() class UserModel { UserModel({ this.type, this.bio, this.name, this.interests, this.presentation, this.links, this.location, this.school, this.occupation, this.createdAt, this.lastUpdatedAt, }); final String? type; final String? bio; final String? name; final List<dynamic>? interests; final Presentation? presentation; final Links? links; final Location? location; final School? school; final Occupation? occupation; final int? createdAt; final int? lastUpdatedAt; // 新增copyWith方法 UserModel copyWith({ String? type, String? bio, String? name, List<dynamic>? interests, Presentation? presentation, Links? links, Location? location, School? school, Occupation? occupation, int? createdAt, int? lastUpdatedAt, }) { return UserModel( type: type ?? this.type, bio: bio ?? this.bio, name: name ?? this.name, interests: interests ?? this.interests, presentation: presentation ?? this.presentation, links: links ?? this.links, location: location ?? this.location, school: school ?? this.school, occupation: occupation ?? this.occupation, createdAt: createdAt ?? this.createdAt, lastUpdatedAt: lastUpdatedAt ?? this.lastUpdatedAt, ); } factory UserModel.fromJson(Map<String, dynamic> json) => _$UserModelFromJson(json); Map<String, dynamic> toJson() => _$UserModelToJson(this); }
如果嫌手写copyWith繁琐,可以用freezed等代码生成库自动生成该方法,逻辑与上述手写版本一致。
2. 更新用户信息时,不要从零新建UserModel,而是基于接口拉取到的原有用户实例调用copyWith,只传入需要修改的字段:
// 假设原有用户数据存储在originalUser变量中,需要修改name为新值 final updatedUser = originalUser.copyWith(name: "新的用户名"); // 此时序列化updatedUser.toJson(),除name字段为新值外,其余字段均保留原有值,不会出现null
注意:如果业务需要支持把某个字段主动设置为null传给接口,上述copyWith默认写法无法区分「未传该字段」和「主动传null」,需要额外增加参数标记,普通资料更新场景下该写法完全够用。
方案2:修改toJson逻辑,序列化时自动合并原有数据
如果不想调整上层更新数据的逻辑,可以在序列化阶段做兜底,给UserModel增加原始数据存储能力,toJson时自动用原有值覆盖null字段:
- 修改
UserModel,增加私有字段存储从接口拉取到的原始Json数据,调整反序列化和序列化逻辑:
@JsonSerializable() class UserModel { UserModel({ this.type, this.bio, this.name, this.interests, this.presentation, this.links, this.location, this.school, this.occupation, this.createdAt, this.lastUpdatedAt, }); // 存储从接口获取的原始数据 Map<String, dynamic>? _originalJson; final String? type; final String? bio; final String? name; final List<dynamic>? interests; final Presentation? presentation; final Links? links; final Location? location; final School? school; final Occupation? occupation; final int? createdAt; final int? lastUpdatedAt; factory UserModel.fromJson(Map<String, dynamic> json) { final instance = _$UserModelFromJson(json); // 反序列化时缓存原始json数据 instance._originalJson = Map.of(json); return instance; } Map<String, dynamic> toJson() { final currentJson = _$UserModelToJson(this); // 存在原始数据时,遍历当前序列化结果,将null值替换为原始数据中对应的有效值 if (_originalJson != null) { _originalJson!.forEach((key, originalValue) { if (currentJson[key] == null && originalValue != null) { currentJson[key] = originalValue; } }); } return currentJson; } }
- 该方案不需要修改上层更新逻辑,即便新建
UserModel时只传入了要修改的name字段,序列化时也会自动把其余null字段替换为原始接口返回的有效值。如果原始数据中某个字段本身就是null,不会做替换,完全匹配接口返回的实际情况。
额外注意
当前自动生成的_$UserModelToJson方法中没有包含createdAt和lastUpdatedAt字段,如果接口要求传这两个系统字段,需要检查字段是否被标记为忽略,补充到序列化逻辑中;如果接口不需要这两个字段,可直接忽略。
内容的提问来源于stack exchange,提问作者MElochukwu

