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

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字段。

  1. 给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字段:

  1. 修改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;
  }
}
  1. 该方案不需要修改上层更新逻辑,即便新建UserModel时只传入了要修改的name字段,序列化时也会自动把其余null字段替换为原始接口返回的有效值。如果原始数据中某个字段本身就是null,不会做替换,完全匹配接口返回的实际情况。

额外注意

当前自动生成的_$UserModelToJson方法中没有包含createdAt和lastUpdatedAt字段,如果接口要求传这两个系统字段,需要检查字段是否被标记为忽略,补充到序列化逻辑中;如果接口不需要这两个字段,可直接忽略。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 03:01:43