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

C#泛型定义子属性反序列化JSON遇构造函数异常问题

异常原因分析
  1. System.Text.Json泛型构造函数解析限制:即使给泛型基类添加了[JsonConstructor]标记的无参构造函数,序列化器对泛型类型的构造函数识别存在特殊逻辑——若基类是抽象泛型类,且未显式指定构造函数为public,序列化器可能无法正确定位到无参构造,转而尝试寻找带参数的构造函数,最终抛出异常。
  2. 自定义转换器与构造解析的冲突:你实现的ApiFootballDictOrEmptyArrayJsonConverter若直接应用在基类的Parameters/Errors字段上,可能干扰序列化器对基类构造函数的调用流程,导致构造函数解析逻辑异常。
  3. 子类构造函数的关联问题:如果子类(如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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 19:55:11