C#泛型定义子属性反序列化JSON遇构造函数异常问题
异常原因分析
- System.Text.Json泛型构造函数解析限制:即使给泛型基类添加了
[JsonConstructor]标记的无参构造函数,序列化器对泛型类型的构造函数识别存在特殊逻辑——若基类是抽象泛型类,且未显式指定构造函数为public,序列化器可能无法正确定位到无参构造,转而尝试寻找带参数的构造函数,最终抛出异常。 - 自定义转换器与构造解析的冲突:你实现的
ApiFootballDictOrEmptyArrayJsonConverter若直接应用在基类的Parameters/Errors字段上,可能干扰序列化器对基类构造函数的调用流程,导致构造函数解析逻辑异常。 - 子类构造函数的关联问题:如果子类(如
ApiFootballStatusResponse)未显式调用基类的无参构造函数,序列化器在实例化子类时,可能无法正确触发基类的构造逻辑,进而报错。
无需重复定义公共属性的优化方案
方案1:修正泛型基类构造函数的可见性与标记
确保泛型基类的无参构造函数为public,并保留[JsonConstructor]标记,让System.Text.Json能直接识别:
public abstract class ApiFootballResponseBase<TResponse> where TResponse : IApiFootballResponseType { [JsonConstructor] public ApiFootballResponseBase() { } public string Get { get; set; } [JsonConverter(typeof(ApiFootballDictOrEmptyArrayJsonConverter))] public Dictionary<string, string> Parameters { get; set; } [JsonConverter(typeof(ApiFootballDictOrEmptyArrayJsonConverter))] public Dictionary<string, string> Errors { get; set; } public int Results { get; set; } public TResponse Response { get; set; } }
方案2:自定义泛型全局转换器绕过构造函数问题
实现针对ApiFootballResponseBase<TResponse>的泛型转换器,手动解析固定字段并反序列化Response,完全不依赖构造函数逻辑:
public class ApiFootballResponseConverter<TResponse> : JsonConverter<ApiFootballResponseBase<TResponse>> where TResponse : IApiFootballResponseType { private readonly ApiFootballDictOrEmptyArrayJsonConverter _dictConverter = new(); public override ApiFootballResponseBase<TResponse> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { using var doc = JsonDocument.ParseValue(ref reader); var root = doc.RootElement; return new ApiFootballResponseBase<TResponse> { Get = root.GetProperty("Get").GetString(), Results = root.GetProperty("Results").GetInt32(), Parameters = JsonSerializer.Deserialize<Dictionary<string, string>>( root.GetProperty("Parameters").GetRawText(), options.WithConverter(_dictConverter)), Errors = JsonSerializer.Deserialize<Dictionary<string, string>>( root.GetProperty("Errors").GetRawText(), options.WithConverter(_dictConverter)), Response = JsonSerializer.Deserialize<TResponse>(root.GetProperty("Response").GetRawText(), options) }; } public override void Write(Utf8JsonWriter writer, ApiFootballResponseBase<TResponse> value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WriteString("Get", value.Get); writer.WriteNumber("Results", value.Results); JsonSerializer.Serialize(writer, value.Parameters, options.WithConverter(_dictConverter)); JsonSerializer.Serialize(writer, value.Errors, options.WithConverter(_dictConverter)); JsonSerializer.Serialize(writer, value.Response, options); writer.WriteEndObject(); } } // 扩展方法简化转换器注册 public static class JsonSerializerOptionsExtensions { public static JsonSerializerOptions WithConverter(this JsonSerializerOptions options, JsonConverter converter) { var newOptions = new JsonSerializerOptions(options); newOptions.Converters.Add(converter); return newOptions; } }
使用时注册转换器即可:
var options = new JsonSerializerOptions(); options.Converters.Add(new ApiFootballResponseConverter<ApiFootballStatusResponse>()); var response = JsonSerializer.Deserialize<ApiFootballResponseBase<ApiFootballStatusResponse>>(jsonContent, options);
方案3:改用C# Record类型重构基类(C#9+)
Record类型默认提供隐式的无参构造逻辑,且System.Text.Json对其序列化支持更友好,无需手动标记构造函数:
public abstract record ApiFootballResponseBase<TResponse> where TResponse : IApiFootballResponseType { public string Get { get; init; } [JsonConverter(typeof(ApiFootballDictOrEmptyArrayJsonConverter))] public Dictionary<string, string> Parameters { get; init; } [JsonConverter(typeof(ApiFootballDictOrEmptyArrayJsonConverter))] public Dictionary<string, string> Errors { get; init; } public int Results { get; init; } public TResponse Response { get; init; } } public record ApiFootballStatusResponse : IApiFootballResponseType { // 自定义响应字段 public string Status { get; init; } public string Version { get; init; } }
内容的提问来源于stack exchange,提问作者Rune Jacobsen
相关产品推荐
相关产品推荐

