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

为何JsonSerializer.Deserialize使用带参数构造函数时无法正常工作?

JsonSerializer.Deserialize 如何配合带参数构造函数使用?

问题场景

示例中Foo1通过属性可正常完成反序列化,但Foo2使用带参数构造函数时触发运行时错误:

public class Foo1
{
    public string propA { get; set; } = "";
    public string propB { get; set; } = "";
    public void Print()
    {
        Console.WriteLine(propA);
        Console.WriteLine(propB);
    }
}
public class Foo2
{
    [JsonConstructor]
    public Foo2(string propA, string propB)
    {
        Console.WriteLine(propA);
        Console.WriteLine(propB);
    }
}
static void TestDeserialize()
{
    string json = """{"propA":"aaa","propB":"bbb"}""";

    Foo1? foo1 = JsonSerializer.Deserialize<Foo1>(json);
    foo1.Print();

    // 错误发生在此行:
    Foo2? foo2 = JsonSerializer.Deserialize<Foo2>(json);
}

错误信息

发生未处理的异常类型'System.InvalidOperationException',位于System.Text.Json.dll中
Foo2类型的反序列化构造函数中的每个参数必须在反序列化时绑定到对象的属性或字段。每个参数名称必须与对象的属性或字段匹配,匹配不区分大小写。

用户提出疑问:构造函数参数明明和JSON属性一一对应,为何报错?如果要求参数必须绑定到C#属性,这似乎不合理——反序列化本质是调用构造函数,不该强制关联类属性,而且这类问题应该在编译阶段就被检测到,而非运行时才报错。


原因分析

System.Text.Json的默认反序列化规则是:标记了[JsonConstructor]的构造函数,其每个参数必须对应类中存在的可访问属性或字段。这一设计是为了确保反序列化后的对象状态可被正确维护,即使构造函数没有显式将参数赋值给类成员。


解决方法

1. 添加与构造函数参数对应的类成员

最简单的方式是为Foo2添加和构造函数参数同名的属性(可以是只读属性),让序列化器能找到参数对应的类成员:

public class Foo2
{
    // 添加对应只读属性
    public string propA { get; }
    public string propB { get; }

    [JsonConstructor]
    public Foo2(string propA, string propB)
    {
        this.propA = propA;
        this.propB = propB;
        Console.WriteLine(propA);
        Console.WriteLine(propB);
    }
}

2. 自定义JsonConverter跳过成员绑定检查

如果不想添加类属性,可以编写自定义转换器,手动解析JSON并调用构造函数,绕开默认的参数绑定规则:

public class Foo2Converter : JsonConverter<Foo2>
{
    public override Foo2? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        using JsonDocument doc = JsonDocument.ParseValue(ref reader);
        var root = doc.RootElement;
        
        string propA = root.TryGetProperty("propA", out var aProp) ? aProp.GetString() ?? string.Empty : string.Empty;
        string propB = root.TryGetProperty("propB", out var bProp) ? bProp.GetString() ?? string.Empty : string.Empty;
        
        return new Foo2(propA, propB);
    }

    public override void Write(Utf8JsonWriter writer, Foo2 value, JsonSerializerOptions options)
    {
        // 如果需要序列化Foo2,在此实现逻辑(示例假设propA/propB可访问)
        writer.WriteStartObject();
        writer.WriteString("propA", value.propA);
        writer.WriteString("propB", value.propB);
        writer.WriteEndObject();
    }
}

使用时在序列化选项中添加转换器:

var options = new JsonSerializerOptions();
options.Converters.Add(new Foo2Converter());
Foo2? foo2 = JsonSerializer.Deserialize<Foo2>(json, options);

3. 升级到.NET 7+(可选)

.NET 7及以上版本的System.Text.Json新增了构造函数参数直接绑定JSON属性的支持,无需对应类成员。只需确保参数名与JSON键匹配(大小写不敏感),即可直接反序列化:

// .NET 7+ 环境下可直接运行原Foo2代码,无需额外修改
Foo2? foo2 = JsonSerializer.Deserialize<Foo2>(json);

如果JSON中存在未匹配的键,可配置选项跳过未映射成员:

var options = new JsonSerializerOptions
{
    UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip
};
Foo2? foo2 = JsonSerializer.Deserialize<Foo2>(json, options);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 18:55:20