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

如何让Json.NET反序列化带主构造函数的C# 9 Record类型?

解决C# 9 Record类型在Json.NET反序列化时的构造函数选择问题

遇到这种情况确实头疼——record的简洁语法带来了便利,但和Json.NET的构造函数解析逻辑撞了车。这里有几个可行的方案,既能保留record的特性,又能解决序列化问题:

方案1:将自定义构造函数替换为静态工厂方法

这是最简洁的方案,不需要改动record的核心声明,只是把双参构造换成静态创建方法,这样Json.NET只会看到主构造函数,自然会用它来反序列化。

public record SomethingHappenedEvent(Guid Id, object TheThing, string WhatHappened)
{
    // 用静态工厂方法替代双参构造函数
    public static SomethingHappenedEvent Create(object theThing, string whatHappened)
        => new SomethingHappenedEvent(Guid.NewGuid(), theThing, whatHappened);
}

调用方代码只需要改成:

var record = SomethingHappenedEvent.Create("roof", "caught fire");

反序列化逻辑完全不需要改动,而且保留了record的所有值类型相等性特性,也避免了调用端重复生成Guid的代码。

方案2:显式声明record的构造函数并标记[JsonConstructor]

如果你还是想保留构造函数的调用方式,可以把record的简洁语法改成显式构造函数和属性声明,这样就能给主构造函数加上[JsonConstructor]标记,明确告诉Json.NET用哪个构造函数反序列化。

public record SomethingHappenedEvent
{
    public Guid Id { get; init; }
    public object TheThing { get; init; }
    public string WhatHappened { get; init; }

    // 给主构造函数标记JsonConstructor
    [JsonConstructor]
    public SomethingHappenedEvent(Guid id, object theThing, string whatHappened)
    {
        Id = id;
        TheThing = theThing;
        WhatHappened = whatHappened;
    }

    // 保留双参构造函数供调用方使用
    public SomethingHappenedEvent(object theThing, string whatHappened) 
        : this(Guid.NewGuid(), theThing, whatHappened) { }
}

这种写法比简洁版多了几行代码,但比普通类还是简洁很多,而且完全符合Json.NET的要求,反序列化时会优先使用标记了[JsonConstructor]的构造函数。

方案3:自定义ContractResolver强制选择正确的构造函数

如果不想修改record的代码,可以自定义一个ContractResolver,让Json.NET在遇到多个构造函数时,自动选择参数数量最多的那个(也就是你的主构造函数),或者选择参数名称与JSON属性匹配度最高的构造函数。

public class RecordContractResolver : DefaultContractResolver
{
    protected override JsonConstructorInfo ResolveConstructor(Type type, JsonPropertyCollection properties)
    {
        var constructorInfos = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
        // 优先选择参数数量最多的构造函数
        var bestConstructor = constructorInfos.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault();
        
        if (bestConstructor != null)
        {
            return CreateConstructorInfo(bestConstructor);
        }
        
        return base.ResolveConstructor(type, properties);
    }
}

然后在序列化/反序列化时指定这个Resolver:

var settings = new JsonSerializerSettings
{
    ContractResolver = new RecordContractResolver()
};

var record = new SomethingHappenedEvent("roof", "caught fire");
var json = JsonConvert.SerializeObject(record, settings);
var otherSideRecord = JsonConvert.DeserializeObject<SomethingHappenedEvent>(json, settings);

这个方案的好处是不需要修改任何record类型的代码,适合有大量record需要处理的场景,但要注意如果后续record的构造函数有变化,可能需要调整Resolver的逻辑。

补充:System.Text.Json的解决方案

如果你切换到System.Text.Json,思路是类似的:要么用静态工厂方法,要么显式声明构造函数并标记[JsonConstructor]。另外,在.NET 6及以上版本,System.Text.Json对record的支持更友好,如果你能升级框架,也可以尝试直接使用默认配置,它会优先匹配与JSON属性对应的主构造函数(前提是没有多个匹配的构造函数)。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:15:12