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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 21:24:56