ASP.NET Core反序列化:子属性全为null时将父属性设为null
问题与解决方案
问题场景
在ASP.NET Core 8 Minimal API中,CreatePerson接口接收如下JSON:
{ "name": "Peter", "residence": { "country": null, "status": null } }
该JSON映射到以下不可变记录类:
public record CreatePersonRequest( string name, Residence? residence ); public record Residence( string Country, string Status );
当前反序列化后,CreatePersonRequest.Residence会被实例化,但Country和Status均为null。需求是当residence的所有子属性都为null时,直接将CreatePersonRequest.Residence设为null,同时保持类的不可变性。
现有JSON配置如下:
services.Configure<JsonOptions>(options => { options.SerializerOptions.PropertyNameCaseInsensitive = true; options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower; options.SerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower; });
尝试过JsonIgnoreCondition.WhenWritingNull和依赖setter的TypeInfoResolver方案,均无法满足需求。
解决方案1:自定义Residence类型Json转换器
通过自定义JsonConverter<Residence>,在反序列化时判断residence对象的所有属性是否为null,若是则返回null,否则正常反序列化。
实现转换器
public class ResidenceConverter : JsonConverter<Residence> { public override Residence? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { // 将JSON对象反序列化为临时字典,便于检查属性值 var tempDict = JsonSerializer.Deserialize<Dictionary<string, object?>>(ref reader, options); if (tempDict == null) return null; // 检查所有属性是否为null bool allPropertiesNull = tempDict.Values.All(val => val == null); if (allPropertiesNull) return null; // 将临时字典重新序列化为JSON字符串,再反序列化为Residence实例 var jsonString = JsonSerializer.Serialize(tempDict, options); return JsonSerializer.Deserialize<Residence>(jsonString, options); } public override void Write(Utf8JsonWriter writer, Residence value, JsonSerializerOptions options) { JsonSerializer.Serialize(writer, value, options); } }
注册转换器
在服务配置中添加自定义转换器:
services.Configure<JsonOptions>(options => { options.SerializerOptions.PropertyNameCaseInsensitive = true; options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower; options.SerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower; // 注册自定义转换器 options.SerializerOptions.Converters.Add(new ResidenceConverter()); });
解决方案2:通过TypeInfoResolver修改构造函数参数
利用不可变记录类的构造函数注入特性,在实例化CreatePersonRequest前修改residence参数值,无需修改类的可变性。
实现类型信息修改器
public static void AdjustResidenceDeserialization(JsonTypeInfo typeInfo) { if (typeInfo.Type != typeof(CreatePersonRequest)) return; // 获取原构造函数实例化逻辑 var originalConstructor = typeInfo.CreateObject as Func<object?[], object>; if (originalConstructor == null) return; // 替换实例化逻辑,调整residence参数 typeInfo.CreateObject = args => { // args[1]对应CreatePersonRequest构造函数的residence参数 var residence = args[1] as Residence; if (residence != null && residence.Country == null && residence.Status == null) { // 将全null的residence替换为null args[1] = null; } return originalConstructor(args); }; }
注册修改器
在服务配置中配置TypeInfoResolver:
services.Configure<JsonOptions>(options => { options.SerializerOptions.PropertyNameCaseInsensitive = true; options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower; options.SerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower; options.SerializerOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver() { Modifiers = { AdjustResidenceDeserialization } }; });
方案对比
- 自定义转换器:全局生效,所有
Residence类型的反序列化都会触发判断,适合多处有相同需求的场景。 - TypeInfo修改器:仅针对
CreatePersonRequest生效,精准控制范围,不会影响其他地方的Residence反序列化。
内容的提问来源于stack exchange,提问作者Hanz LLanto
相关产品推荐
相关产品推荐

