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

.NET API返回报错Nullable object must have a value的解决求助

解决第三方NuGet类未初始化可空Decimal属性导致的API序列化失败问题

问题场景

使用自定义框架开发API,通过ProcessRequestAsync方法返回响应,核心代码如下:

[HttpGet]
[Route("order/{orderId:long}")]
public async Task<IActionResult> GetOrderAsync(int orderId)
{
    try
    {
         return await ProcessRequestAsync(
             requestParameter,
             requestMethod,
             response => Ok(response) // 此处触发序列化失败
         );
    }
    catch (Exception ex)
    {
         // 异常处理代码
    }
}

调用接口时Swagger返回错误:

{
  "Message": "Nullable object must have a value."
}

排查结果

  • 响应对象非空,包含有效业务数据
  • 响应为多层嵌套的复杂对象
  • 问题根源:嵌套的Object3类(来自第三方NuGet包)存在未初始化的decimal?类型属性,且无法直接给该属性添加[JsonIgnore]特性;全局配置序列化忽略null值未生效

已尝试的无效方案

全局配置Json序列化忽略null值:

private static void ConfigureServices(IServiceCollection serviceCollection, IConfiguration config)
{
    serviceCollection.AddControllers()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
        options.JsonSerializerOptions.IgnoreReadOnlyProperties = true;
        options.JsonSerializerOptions.WriteIndented = true;
        options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
    });
}

同时尝试过自定义转换器将null转为默认值,但未生效且需为所有类型编写转换器,成本过高。

可行解决办法

方案1:匿名类型投影过滤问题属性

返回响应时,通过匿名类型只序列化需要的属性,跳过第三方类中的问题属性:

response => Ok(new 
{
    OrderId = response.OrderId,
    Object1 = new 
    {
        Object2 = new 
        {
            Object3 = new 
            {
                // 只包含Object3中正常属性,排除未初始化的可空Decimal属性
                ValidProp1 = response.Object1.Object2.Object3.ValidProp1,
                ValidProp2 = response.Object1.Object2.Object3.ValidProp2
            }
        }
    }
})

优点:快速生效,无需修改全局配置;缺点:属性较多时代码量大,维护成本高。

方案2:为第三方类编写自定义JsonConverter

针对Object3类单独编写转换器,处理可空Decimal属性:

public class Object3Converter : JsonConverter<Object3>
{
    public override Object3 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return JsonSerializer.Deserialize<Object3>(ref reader, options);
    }

    public override void Write(Utf8JsonWriter writer, Object3 value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();
        // 手动序列化正常属性
        writer.WriteString("ValidProp1", value.ValidProp1);
        writer.WriteString("ValidProp2", value.ValidProp2);
        // 处理可空Decimal属性:有值则序列化,否则跳过
        if (value.NullableDecimalProp.HasValue)
        {
            writer.WriteNumber("NullableDecimalProp", value.NullableDecimalProp.Value);
        }
        writer.WriteEndObject();
    }
}

在配置中注册转换器:

serviceCollection.AddControllers()
.AddJsonOptions(options =>
{
    options.JsonSerializerOptions.Converters.Add(new Object3Converter());
    // 保留原有配置
    options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
    options.JsonSerializerOptions.IgnoreReadOnlyProperties = true;
    options.JsonSerializerOptions.WriteIndented = true;
    options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
});

优点:全局生效,无需修改业务代码;缺点:仅需针对特定第三方类编写一次转换器。

方案3:使用DTO隔离第三方类

定义与响应结构一致的DTO类,将第三方对象映射到DTO时处理问题属性:

// 定义DTO类
public class OrderResponseDto
{
    public long OrderId { get; set; }
    public Object1Dto Object1 { get; set; }
}

public class Object1Dto
{
    public Object2Dto Object2 { get; set; }
}

public class Object2Dto
{
    public Object3Dto Object3 { get; set; }
}

public class Object3Dto
{
    public string ValidProp1 { get; set; }
    public string ValidProp2 { get; set; }
    public decimal? NullableDecimalProp { get; set; }
}

返回响应前完成映射(可使用AutoMapper简化):

response => 
{
    var dto = new OrderResponseDto
    {
        OrderId = response.OrderId,
        Object1 = new Object1Dto
        {
            Object2 = new Object2Dto
            {
                Object3 = new Object3Dto
                {
                    ValidProp1 = response.Object1.Object2.Object3.ValidProp1,
                    ValidProp2 = response.Object1.Object2.Object3.ValidProp2,
                    NullableDecimalProp = response.Object1.Object2.Object3.NullableDecimalProp ?? 0
                }
            }
        }
    };
    return Ok(dto);
}

优点:解耦第三方依赖,业务变更更灵活;缺点:需定义额外DTO类,初期有一定工作量。

方案4:反射动态设置默认值(临时应急)

通过反射在序列化前为问题属性设置默认值:

response => 
{
    var object3 = response.Object1.Object2.Object3;
    var prop = object3.GetType().GetProperty("NullableDecimalProp");
    if (prop != null && prop.PropertyType == typeof(decimal?))
    {
        var value = prop.GetValue(object3);
        if (value == null)
        {
            prop.SetValue(object3, 0m);
        }
    }
    return Ok(response);
}

优点:快速应急,无需修改全局配置或新增类;缺点:反射性能差,不适合高并发场景,属性名称硬编码易出错。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 19:03:22