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

.NET 6.0 JSON反序列化属性丢失 序列化配置引发登录异常

问题背景

项目基于 .NET 6.0 开发,使用 PriceVM 类反序列化第三方接口返回的JSON数据后,Postman调用接口的输出结果中 Show、Ticket 字段缺失。

第三方接口原始返回JSON

未使用模型绑定反序列化时,接口返回的原始JSON结构如下:

{
    "traceId": "fhregtkgregtireuiti3t",
     "data": {
        "airPrice":[
            {
                "key":"kjewqerlewqwwq",
                "show":true,
                "showSpecified":true,
                "departure":"2022-06-28T19:08:00.000+01:00",
                "solution":[
                    {
                        "ticket":false,
                        "ticketSpecified":false,
                        "origin":"JED",
                        "exchange":false
                    },
                    {
                        "ticket":false,
                        "ticketSpecified":false,
                        "origin":"MND",
                        "exchange":false
                    }
                ]   
            }
        ]
     }
}

自定义实体类定义

对应JSON结构定义的实体类代码如下:

public class PriceVM{
 [JsonProperty("traceId")]
 public string TraceId {get;set;}

 [JsonProperty("data")]
 public Data Data {get;set;}
}

public class Data {
 [JsonProperty("airPrice")]
 public IEnumerable<AirPrice> AirPrice {get;set;}
}

public class AirPrice{
 [JsonProperty("airPrice")]
 public string Key {get;set;}

 [JsonProperty("show")]
 public bool Show {get;set;}

 [JsonProperty("showSpecified")]
 public bool ShowSpecified {get;set;}

 [JsonProperty("departure")]
 public DateTime Departure {get;set;}

 [JsonProperty("solution")]
 public List<Solution> Solution {get;set;}
} 

public class Solution{
 [JsonProperty("ticket")]    
 public bool Ticket {get;set;}

 [JsonProperty("ticketSpecified")]    
 public bool TicketSpecified {get;set;}

 [JsonProperty("origin")]
 public string Origin {get;set;}

 [JsonProperty("exchange")]
 public bool Exchange {get;set;}
}

接口调用逻辑

调用第三方API、反序列化返回结果的代码如下:

public async Task<PriceVM> Handle(PriceQuery request)
{
 var response= await client.PostAsJsonAsync<ADT>("url",request);
 response.EnsureSuccessStatusCode();
 string responseBody = await response.Content.ReadAsStringAsync();
 
 var airPrice = JsonConvert.DeserializeObject<PriceVM>(responseBody, new JsonSerializerSettings
 {
     DateParseHandling = DateParseHandling.None,    
 });
 return airPrice;
}

控制器端点定义:

[HttpPost]
[Route("price")]
public async Task<ActionResult<PriceVM>> AirPrice( [FromBody] ADT dto)
{
    var result = await pricesrvice.Handle(dto);
    return Ok(result);
}

异常表现

使用强类型反序列化后,接口返回结果缺失Show、Ticket属性,返回内容如下:

{
        "traceId": "fhregtkgregtireuiti3t",
         "data": {
            "airPrice":[
                {
                    "key":"kjewqerlewqwwq",
                    "showSpecified":true,
                    "departure":"2022-06-28T19:08:00.000+01:00",
                    "solution":[
                        {
                            "ticketSpecified":false,
                            "origin":"JED",
                            "exchange":false
                        },
                        {
                            "ticketSpecified":false,
                            "origin":"MND",
                            "exchange":false
                        }
                    ]   
                }
            ]
         }
    }

调试时确认控制器内的result对象实际包含Show、Ticket属性和对应值;如果使用非强类型方式反序列化,输出结果可以包含所有字段:

var airPrice = JsonConvert.DeserializeObject(responseBody, new JsonSerializerSettings
{
    DateParseHandling = DateParseHandling.None,    
});

已尝试方案与副作用

排查确认问题与属性名+Specified格式的配对属性有关(例如ticket/ticketSpecified、show/showSpecified),因此在Program.cs中添加了如下NewtonsoftJson配置:

builder.Services.AddControllers(options =>
                options.Filters.Add<ApiExceptionFilterAttribute>())
                    .AddFluentValidation()
.AddNewtonsoftJson(options =>
{
    options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
    // 新增配置
    options.SerializerSettings.ContractResolver = new DefaultContractResolver()
    {
        IgnoreIsSpecifiedMembers = false,
    };
});

添加该配置后,价格查询接口可以正常返回所有属性,但会导致UI端登录功能失效,开发、生产环境控制台无任何错误输出;注释掉上述ContractResolver配置段后,登录功能恢复正常。


问题根因
  1. 字段缺失的核心原因是Newtonsoft.Json默认的xxxSpecified约定规则:当类中存在和布尔属性同名、后缀为Specified的布尔属性时,序列化阶段会自动检查xxxSpecified的值,如果该值为false,就会跳过对应xxx属性的序列化。你的场景中ticketSpecified值为false,因此序列化时ticket字段被自动忽略。
  2. 全局配置导致登录失败的原因是直接new了默认的DefaultContractResolver,覆盖了系统原有的契约解析配置(包括默认驼峰命名策略、身份认证模型的序列化规则),导致登录等其他接口的返回字段名、序列化逻辑发生无感知变化,前端无法正确读取认证相关字段,最终登录失败。
可行解决方案

不要全局替换ContractResolver,采用小范围配置的方式解决问题,避免影响其他业务接口:

方案1:类级别特性标注(推荐)

直接在涉及Specified字段的实体类上添加[JsonObject]特性,指定仅这两个类序列化时忽略Specified成员检查,不需要修改全局配置,完全不影响登录等其他接口。注意同时修正AirPrice类中Key属性的映射错误(原代码错误映射到了airPrice字段):

[JsonObject(IgnoreIsSpecifiedMembers = true)]
public class AirPrice{
 [JsonProperty("key")] // 修正原映射错误
 public string Key {get;set;}

 [JsonProperty("show")]
 public bool Show {get;set;}

 [JsonProperty("showSpecified")]
 public bool ShowSpecified {get;set;}

 [JsonProperty("departure")]
 public DateTime Departure {get;set;}

 [JsonProperty("solution")]
 public List<Solution> Solution {get;set;}
} 

[JsonObject(IgnoreIsSpecifiedMembers = true)]
public class Solution{
 [JsonProperty("ticket")]    
 public bool Ticket {get;set;}

 [JsonProperty("ticketSpecified")]    
 public bool TicketSpecified {get;set;}

 [JsonProperty("origin")]
 public string Origin {get;set;}

 [JsonProperty("exchange")]
 public bool Exchange {get;set;}
}

方案2:定义专用输出DTO

额外定义接口输出专用的DTO类,去掉ShowSpecified、TicketSpecified这类第三方接口返回的冗余字段,反序列化得到第三方数据后,手动映射到输出DTO再返回,从根源上避免触发Specified命名约定,不需要调整任何序列化配置,对现有业务零侵入。

// 输出专用DTO,无Specified后缀字段
public class PriceVMOuput{
    public string TraceId {get;set;}
    public DataOutput Data {get;set;}
}
public class DataOutput{
    public IEnumerable<AirPriceOutput> AirPrice {get;set;}
}
public class AirPriceOutput{
    public string Key {get;set;}
    public bool Show {get;set;}
    public DateTime Departure {get;set;}
    public List<SolutionOutput> Solution {get;set;}
}
public class SolutionOutput{
    public bool Ticket {get;set;}
    public string Origin {get;set;}
    public bool Exchange {get;set;}
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 09:27:10