System.Text.Json泛型反序列化Schema不匹配问题问询
针对你遇到的API返回JSON与TResult Schema完全不匹配时,反序列化生成空属性实例的问题,以下是几种更优的处理方式——这并非C#泛型的局限,而是System.Text.Json默认行为的设计选择,可通过配置或扩展解决:
1. 启用未知属性检测,强制不匹配时抛出异常
System.Text.Json默认会忽略目标类型中不存在的JSON属性(IgnoreUnknownProperties默认值为true),将其改为false后,当JSON包含TResult未定义的属性时,反序列化会直接抛出JsonException,这样就能在SendContent方法中捕获异常并返回错误结果。
修改后的SendContent方法示例:
protected async Task<Result<TResult?>> SendContent<TResult>(string endpoint, HttpMethod httpMethod, RequestHeaders headers, HttpContent httpContent, CancellationToken cancellationToken) where TResult : class { // httpRequestMessage created here. try { using var httpResponse = await _httpClient.SendAsync(httpRequestMessage, cancellationToken).ConfigureAwait(false); var jsonString = await httpResponse.Content.ReadAsStringAsync(); var options = new JsonSerializerOptions { IgnoreUnknownProperties = false // 禁用忽略未知属性 }; var result = JsonSerializer.Deserialize<TResult>(jsonString, options); // 后续处理逻辑 } catch (JsonException ex) { // 捕获反序列化异常,返回错误结果 return Result.Failure<TResult?>("Schema不匹配:" + ex.Message); } }
注意:此方法会在JSON存在任何未知属性时抛出异常,若API允许返回额外属性(部分匹配场景),则不适用。
2. 使用数据注解验证必填属性
给TResult类的必填属性添加[Required]注解,反序列化完成后通过Validator验证对象是否符合要求,若必填属性未被赋值则判定为Schema不匹配。
首先给TestReturnObject添加注解:
using System.ComponentModel.DataAnnotations; public class TestReturnObject { [Required] public int Id { get; set; } [Required] public string Name { get; set; } = string.Empty; // 避免null引用 }
然后在SendContent方法中添加验证逻辑:
protected async Task<Result<TResult?>> SendContent<TResult>(string endpoint, HttpMethod httpMethod, RequestHeaders headers, HttpContent httpContent, CancellationToken cancellationToken) where TResult : class { // httpRequestMessage created here. try { using var httpResponse = await _httpClient.SendAsync(httpRequestMessage, cancellationToken).ConfigureAwait(false); var jsonString = await httpResponse.Content.ReadAsStringAsync(); var result = JsonSerializer.Deserialize<TResult>(jsonString); if (result != null) { var validationContext = new ValidationContext(result); var validationResults = new List<ValidationResult>(); if (!Validator.TryValidateObject(result, validationContext, validationResults, validateAllProperties: true)) { // 验证失败,返回错误 var errorMsg = string.Join("; ", validationResults.Select(r => r.ErrorMessage)); return Result.Failure<TResult?>("必填属性未赋值:" + errorMsg); } } // 后续处理逻辑 } catch (Exception ex) { // 处理其他异常 return Result.Failure<TResult?>(ex.Message); } }
优点:无需修改反序列化核心逻辑,只需给类添加注解,适合有明确必填项的场景;缺点:无法检测JSON与属性完全不匹配的情况(比如JSON属性全错,但TResult的属性有默认值时,验证可能通过)。
3. 自定义检测逻辑:验证JSON与TResult的属性交集
先将JSON解析为JsonElement,提取所有属性名,再获取TResult的所有公共属性名,检查两者是否有交集。若没有交集,则判定为Schema完全不匹配。
示例代码:
protected async Task<Result<TResult?>> SendContent<TResult>(string endpoint, HttpMethod httpMethod, RequestHeaders headers, HttpContent httpContent, CancellationToken cancellationToken) where TResult : class { // httpRequestMessage created here. try { using var httpResponse = await _httpClient.SendAsync(httpRequestMessage, cancellationToken).ConfigureAwait(false); var jsonString = await httpResponse.Content.ReadAsStringAsync(); // 先解析为JsonElement,提取属性名 using var doc = JsonDocument.Parse(jsonString); var jsonProperties = doc.RootElement.EnumerateObject().Select(p => p.Name).ToHashSet(); // 获取TResult的公共属性名 var typeProperties = typeof(TResult).GetProperties(BindingFlags.Public | BindingFlags.Instance) .Select(p => p.Name).ToHashSet(); // 检查是否有属性交集(可根据需求添加大小写忽略逻辑) if (!jsonProperties.Intersect(typeProperties).Any()) { return Result.Failure<TResult?>("JSON与目标类型Schema完全不匹配"); } // 正常反序列化 var result = JsonSerializer.Deserialize<TResult>(jsonString); // 后续处理逻辑 } catch (Exception ex) { return Result.Failure<TResult?>(ex.Message); } }
优点:精准检测完全不匹配的场景,不会误判部分匹配的情况;缺点:需要额外解析JSON,有少量性能开销。
你可以根据业务场景选择合适的方案:如果API严格遵循Schema,第一种方法最直接;如果有明确必填项,第二种方法更简洁;如果需要区分完全不匹配和部分匹配,第三种方法最灵活。
内容的提问来源于stack exchange,提问作者user18270057

