我可以让Json.net用“0”反序列化C#9记录类型吗;“初级”;构造函数,就好像它有[JsonConstructor]?

我可以让Json.net用“0”反序列化C#9记录类型吗;“初级”;构造函数,就好像它有[JsonConstructor]?,c#,json,json.net,c#-9.0,C#,Json,Json.net,C# 9.0,使用.NET 5.0上的C#9,我有很多记录类型,如下所示: public record SomethingHappenedEvent(Guid Id, object TheThing, string WhatHappened) { public SomethingHappenedEvent(object theThing, string whatHappened) : this(Guid.NewGuid(), theThing, whatHappened) {

使用.NET 5.0上的C#9,我有很多记录类型,如下所示:

public record SomethingHappenedEvent(Guid Id, object TheThing, string WhatHappened)
{
    public SomethingHappenedEvent(object theThing, string whatHappened)
        : this(Guid.NewGuid(), theThing, whatHappened)
    { }
}
正如您所料,它们被序列化并发送到其他地方进行处理。发送方调用双参数构造函数并获取新Id,但反序列化程序需要使用记录声明隐含的“主”三参数构造函数。 我使用的是Newtonsoft Json.NET,我真的希望它能起作用:

        var record = new SomethingHappenedEvent("roof", "caught fire");
        var json = JsonConvert.SerializeObject(record);
        var otherSideRecord = JsonConvert.DeserializeObject<SomethingHappenedEvent>(json);
        Assert.AreEqual(record, otherSideRecord);
但这会尝试将属性应用于类型,这是无效的。 但这是C#中的一个语法错误

我目前的解决方案是将这些类型保留为普通类,并使用所有额外的样板文件。我也知道我可以省略自定义构造函数,让我的调用者生成ID。这是因为json.net只能找到一个构造函数。这当然是简明扼要的!但是我不喜欢在所有的调用站点重复代码,即使在这种情况下代码很小

public record SomethingHappenedEvent(Guid Id, object TheThing, string WhatHappened) { }

FWIW听起来System.Text.Json也有同样的限制。

首先,您只需在创建自己的构造函数时执行此操作。这是因为实例化时它不知道使用哪一个

其次,请注意(默认情况下),反序列化程序将使用属性和构造函数名称,并覆盖您在实际构造函数类型中忽略的名称。此外,构造函数中的每个参数必须在反序列化时绑定到对象属性或字段。如果您没有意识到这些格式可能会导致细微错误,但这不仅仅限于记录

除此之外,你把属性放错地方了。简而言之,属性需要位于构造函数上

胡编乱造的荒谬例子:

给定的

public record TestRecord(Guid Id)
{
   [JsonConstructor]
   public TestRecord(object theThing, string whatHappened) : this(Guid.NewGuid())
   {
   }
}
测试

var record = new TestRecord(Guid.NewGuid());
var json = JsonConvert.SerializeObject(record,Formatting.Indented);
Console.WriteLine(json);
var otherSideRecord = JsonConvert.DeserializeObject<TestRecord>(json);

// note this paradoxically still works, because it has overwritten the ID
Console.WriteLine(record == otherSideRecord);
{
  "Id": "2905cfaf-d13d-4df1-af83-e4dcde20d44f"
}
True
请注意,该属性也适用于
Text.Json

var json = JsonSerializer.Serialize(record);
var otherSideRecord = JsonSerializer.Deserialize<TestRecord>(json);
var json=JsonSerializer.Serialize(记录);
var otherSideRecord=JsonSerializer.Deserialize(json);

我在试验时遇到了另一个选项。我把它留在这里,以防对别人有用。您可以将自定义构造函数转换为工厂方法。这只剩下一个构造函数供反序列化程序查找

public record SomethingHappenedEvent(Guid Id, object TheThing, string WhatHappened)
{
    public static SomethingHappenedEvent Create(object theThing, string whatHappened)
        => new(Guid.NewGuid(), theThing, whatHappened);
}
它改变了呼叫站点的语法。只需调用Create而不是new:

var myEvent = SomethingHappenedEvent.Create("partyPeople", "gotDown");

当然,您可以将静态工厂方法推送到一个单独的工厂对象,如果您的对象构造具有更丰富的依赖项,这可能会很有用。

您可以使用该属性添加一个默认构造函数。Json.Net将填充属性

如果需要,可以将其设置为私有,以便该类的其他用户都不能错误地使用它。Json.Net仍会找到它:

public record SomethingHappenedEvent(Guid Id, object TheThing, string WhatHappened)
{
    [JsonConstructor]
    private SomethingHappenedEvent()
    { }

    public SomethingHappenedEvent(object theThing, string whatHappened)
        : this(Guid.NewGuid(), theThing, whatHappened)
    { }
}

有趣。这实际上在示例中起作用,并且可能在许多实际案例中起作用。但这有点奇怪。反序列化程序使用的2参数构造函数是错误的,因此对其使用属性似乎很奇怪。事实证明,在这里它实际上是正常的,因为json.net随后调用Id setter,覆盖接收端Guid.NewGuid生成的值。如果2参数构造函数由于某种原因而不是因为冗余而实际上不正确,则会失败,但这显然是一个更狭隘的情况。@solublefish确实正确,管道只需要知道要创建哪个构造函数。注意这里有不同的用例,但是对于您的,您只需要反序列化您可以采用从到的方法,并使用自定义契约解析器自动调用“最特定”的构造函数。然而,既然有,你需要考虑一下什么时候使用最具体的构造函数。那太好了。一旦拥有CustomContractResolver,各种选项都将可用。例如,您可以实现[DontUseThisConstructor]属性。
var myEvent = SomethingHappenedEvent.Create("partyPeople", "gotDown");
public record SomethingHappenedEvent(Guid Id, object TheThing, string WhatHappened)
{
    [JsonConstructor]
    private SomethingHappenedEvent()
    { }

    public SomethingHappenedEvent(object theThing, string whatHappened)
        : this(Guid.NewGuid(), theThing, whatHappened)
    { }
}