从Newtonsoft.Json迁移到System.Text.Json仍遇Int32转换错误
问题
从Newtonsoft.Json迁移到System.Text.Json后,原本通过配置NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore可以正常处理null值的id字段,现在出现错误:"The JSON value could not be converted to System.Int32"。参考官方建议将Id字段改为可空类型后,问题仍未解决,请求排查错误原因。
此前Newtonsoft配置
.AddNewtonsoftJson(x => { x.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore; })
错误详情
{ "$id":"1", "type":"https://tools.ietf.org/html/rfc9110#section-15.5.1", "title":"One or more validation errors occurred.", "status":400, "errors":{ "$id":"2", "$.id":[ "The JSON value could not be converted to System.Int32. Path: $.id | LineNumber: 0 | BytePositionInLine:10." ] }, "traceId":"00-e707e5d67aedda844c575987294eaab8-50ccf58df2364c2a-00" }
修改后的实体类代码
[Serializable] public abstract class Dto<TId> : DomainEntity<TId> { [Key] public new TId? Id { get => (TId?)base.Id; set => base.Id = value; } } [Serializable] public abstract class Dto : DomainEntity { } [Serializable] public abstract class DomainEntity<TId> : DomainEntity { [Key] public TId? Id { get; set; } } [Serializable] public abstract class DomainEntity { public DateTime CreatedDate { get; set; } public DateTime UpdatedDate { get; set; } } [Serializable] public abstract class CreatedBySomeoneDto : Dto<int> { public string CreatedById { get; set; } public string UpdatedById { get; set; } } [Serializable] public class VgnItmDto : CreatedBySomeoneDto { [Required] [Text(Boost = 2)] public string Name { get; set; } [Required] [Text(Boost = 0.5)] public string CompanyName { get; set; } [MaxLength(3000)] public string Description { get; set; } [Required] public int IsNotVeganCount { get; set; } = 0; [Required] public int IsVeganCount { get; set; } = 0; [Required] public int RatingsCount { get; set; } = 0; public double? Rating { get; set; } [JsonIgnore] private Collection<int> _tags = new Collection<int>(); [Required] [JsonPropertyName("tags")] public Collection<int> Tags => new Collection<int>(_tags); [JsonIgnore] private Collection<int> _secondaryTags = new Collection<int>(); [JsonPropertyName("secondaryTags")] public Collection<int> SecondaryTags => new Collection<int>(_secondaryTags); [MaxLength(20)] [JsonIgnore] private Collection<ImageDto> _images = new Collection<ImageDto>(); [JsonPropertyName("images")] public Collection<ImageDto> Images => new Collection<ImageDto>(_images); public int? PricePoint { get; set; } [Keyword] public string Discriminator { get; set; } [MaxLength(900)] public string WebPage { get; set; } public string Instagram { get; set; } public string Facebook { get; set; } public void UpdateTags(IEnumerable<int> tags) { _tags = new Collection<int>(tags.ToList()); } public void UpdateSecondaryTags(IEnumerable<int> secondaryTags) { _secondaryTags = new Collection<int>(secondaryTags.ToList()); } public void UpdateImages(IEnumerable<ImageDto> images) { _images = new Collection<ImageDto>(images.ToList()); } } public class MenuItmDto : VgnItmDto { }
控制器代码
[Route("[controller]")] [ApiController] public class VgnItmsController<TVgnItm, TVgnItmDto, TService> : ControllerBase where TVgnItm : VgnItm where TVgnItmDto : VgnItmDto where TService : IVgnItmsService<TVgnItm, TVgnItmDto> { protected DbSet<TVgnItm> vgnItms; protected readonly VpDbContext context; protected readonly ExtendedVepoContext extendedContext; protected readonly TService service; public VgnItmsController( TService service) { this.service = service; } [HttpPost("create")] public async Task<ServiceResponse<TVgnItmDto>> AddVgnItm(TVgnItmDto vgnItm) { return await service.Add(vgnItm); } } [Route("menu-items")] [ApiController] public class MenuItmsController : VgnItmsController<MenuItm, MenuItmDto, IMenuItmsService> { public MenuItmsController( VpDbContext context, ExtendedVepoContext extendedContext, IMenuItmsService service) : base(service) { } }
请求JSON体
{ "id":null, "name":"a", "companyName":"a", "description":null, "tags":[ 20 ], "secondaryTags":[ ], "images":[ ], "webPage":null, "instagram":null, "facebook":null, "rating":null, "ratingsCount":0, "isNotVeganCount":0, "isVeganCount":0, "pricePoint":null, "createdDate":null, "updatedDate":null, "createdById":null, "updatedById":null, "discriminator":"MenuItm" }
错误原因及解决方案
错误原因
- 泛型类型参数未指定可空值类型:
CreatedBySomeoneDto继承自Dto<int>,这里的int是非可空值类型。虽然你在Dto<TId>和DomainEntity<TId>中把Id定义为TId?,但当TId是非可空值类型时,System.Text.Json无法正确识别其可空性,仍会尝试将null映射到非可空的int类型。 - System.Text.Json默认不忽略null值:Newtonsoft的
NullValueHandling.Ignore会跳过null值的反序列化,但System.Text.Json默认会处理所有存在的字段,包括null值。即使Id是可空类型,JSON中存在id:null时,若未配置忽略规则,仍会触发类型转换校验。 - 字段隐藏导致的歧义:
Dto<TId>中的Id用new关键字隐藏了基类的Id字段,可能导致System.Text.Json反序列化时优先处理基类的非可空Id字段,引发类型转换错误。
解决方案
修改泛型继承的类型参数为可空值类型
将CreatedBySomeoneDto的继承改为Dto<int?>,明确指定TId为可空整数:[Serializable] public abstract class CreatedBySomeoneDto : Dto<int?> { public string CreatedById { get; set; } public string UpdatedById { get; set; } }配置System.Text.Json忽略null值
在项目配置中添加System.Text.Json的忽略规则,对齐Newtonsoft的行为:builder.Services.AddControllers() .AddJsonOptions(options => { // 序列化和反序列化时都忽略null值 options.JsonSerializerOptions.IgnoreNullValues = true; // 或者仅在值为null时忽略序列化(按需选择) // options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; });移除
new关键字,避免字段隐藏
删除Dto<TId>中重新定义的Id字段,直接使用基类的Id,消除反序列化的歧义:[Serializable] public abstract class Dto<TId> : DomainEntity<TId> { // 直接复用基类的Id字段,无需重新定义 }启用nullable引用类型
在项目的.csproj文件中添加<Nullable>enable</Nullable>,让编译器和System.Text.Json能更准确地识别可空类型:<PropertyGroup> <Nullable>enable</Nullable> </PropertyGroup>
内容的提问来源于stack exchange,提问作者BeniaminoBaggins
相关产品推荐
相关产品推荐

