如何在Dart中建模区分null与未指定字段的PATCH请求体?
处理Dart Freezed中区分“显式null”与“未指定字段”的JSON序列化问题
问题背景
某HTTP API的PATCH请求体JSON Schema如下:
{ "type": "object", "properties": { "propertyOne": { "type": ["string", "null"] }, "propertyTwo": { "type": "string" } } }
合法的请求体示例:
{ "propertyOne": null }
{ "propertyTwo": "test" }
{ "propertyOne": "test", "propertyTwo": "test" }
序列化需求
- 若
propertyOne被显式设为null,JSON输出需包含"propertyOne": null; - 若
propertyOne被初始化为字符串,输出对应键值对; - 若初始化类时未指定
propertyOne,JSON中不包含该字段。
现有方案的问题
当前使用Freezed库的实现如下:
@freezed class PatchRequestBody with _$PatchRequestBody { const factory PatchRequestBody({ @JsonKey(includeIfNull: false) String? propertyOne, @JsonKey(includeIfNull: false) String? propertyTwo }) = _PatchRequestBody; factory PatchRequestBody.fromJson(Map<String, dynamic> json) => _$PatchRequestBodyFromJson(json); }
存在以下问题:
- 调用
PatchRequestBody(propertyOne: null).toJson()会输出空对象,显式null被忽略; - 移除
@JsonKey(includeIfNull: false)后,调用PatchRequestBody(propertyTwo: "test").toJson()会输出{ "propertyOne": null, "propertyTwo": "test" },未指定的字段被转为null,不符合需求。
解决方案
通过自定义可选值包装类配合JsonConverter,实现“未指定”与“显式null”的区分:
1. 实现可选值包装类
class Optional<T> { final T? value; final bool isProvided; // 显式提供值(包括null) const Optional.provided(this.value) : isProvided = true; // 未提供值 const Optional.unprovided() : value = null, isProvided = false; }
2. 实现JSON转换器
class OptionalConverter<T> implements JsonConverter<Optional<T>, dynamic> { const OptionalConverter(); @override Optional<T> fromJson(dynamic json) { // 反序列化时,只要存在字段就标记为已提供 return Optional.provided(json as T?); } @override dynamic toJson(Optional<T> optional) { // 未提供时返回null,配合includeIfNull: false忽略该字段 if (!optional.isProvided) return null; // 已提供时返回实际值(可能为null) return optional.value; } }
3. 修改Freezed模型
@freezed class PatchRequestBody with _$PatchRequestBody { const factory PatchRequestBody({ @OptionalConverter() @JsonKey(includeIfNull: false) Optional<String>? propertyOne, // propertyTwo是必填字符串,直接用required required String propertyTwo, }) = _PatchRequestBody; factory PatchRequestBody.fromJson(Map<String, dynamic> json) => _$PatchRequestBodyFromJson(json); }
4. 使用示例
- 未指定
propertyOne:PatchRequestBody(propertyTwo: "test").toJson(); // 输出: {"propertyTwo": "test"} - 显式设置
propertyOne为null:PatchRequestBody( propertyOne: const Optional.provided(null), propertyTwo: "test" ).toJson(); // 输出: {"propertyOne": null, "propertyTwo": "test"} - 设置
propertyOne为字符串:PatchRequestBody( propertyOne: const Optional.provided("test"), propertyTwo: "test" ).toJson(); // 输出: {"propertyOne": "test", "propertyTwo": "test"}
内容的提问来源于stack exchange,提问作者Codrin
相关产品推荐
相关产品推荐

