.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
相关产品推荐
相关产品推荐

