我可以使用 "primary" 构造函数使 Json.net 反序列化 C# 9 记录类型,就像它具有 [JsonConstructor] 一样吗?
Can I make Json.net deserialize a C# 9 record type with the "primary" constructor, as if it had [JsonConstructor]?
在 .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);
当然不是。它抛出 JsonSerializationException。它找不到正确的构造函数,因为有两个构造函数,既不是默认的零参数构造函数,也没有用 JsonConstructorAttribute 标记。
我的问题真的是“我有什么选择来获得类似的东西?”。这会很棒:
[JsonConstructor]
public record SomethingHappenedEvent(Guid Id, object TheThing, string WhatHappened)
{
public SomethingHappenedEvent(object theThing, string whatHappened)
: this(Guid.NewGuid(), theThing, whatHappened)
{ }
}
但是那试图将属性应用于无效的类型。
这是 C# 中的语法错误,尽管 .
public record SomethingHappenedEvent
[JsonConstructor]
(Guid Id, object TheThing, string WhatHappened)
{
public SomethingHappenedEvent(object theThing, string whatHappened)
: this(Guid.NewGuid(), theThing, whatHappened)
{ }
}
我目前的解决方案是将这些类型保留为普通 类 并使用所有额外的样板。我也知道我可以省略自定义构造函数并让我的调用者生成 ID。这是可行的,因为 json.net 只能找到一个构造函数。这当然很简洁!但我不喜欢在所有调用站点重复代码,即使在这种情况下它很小。
public record SomethingHappenedEvent(Guid Id, object TheThing, string WhatHappened) { }
FWIW 听起来 System.Text.Json 具有相同的限制。
首先,您只需在创建自己的构造函数时执行此操作。这是因为在实例化时它不知道要使用哪个。
其次,请注意(默认情况下)反序列化器将使用 属性 和构造函数名称,并覆盖您在实际构造函数类型中省略的名称。此外,构造函数中的每个参数都必须在反序列化时绑定到对象 属性 或字段。如果您不注意前者,可能会导致细微的错误,但这不仅限于 records.
除此之外,您的属性放错了地方。简而言之,属性需要在 constructor.
胡乱编造的荒谬示例:
给定
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);
我在试验时运行跨过另一个选项。我会把它留在这里以防它对某人有用。您可以将自定义构造函数转换为工厂方法。这只剩下一个构造函数供反序列化器查找。
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 将填充属性。
如果需要,可以将其设为私有,这样 class 的其他用户 none 就可以错误地使用它。 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)
{ }
}
在 .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);
当然不是。它抛出 JsonSerializationException。它找不到正确的构造函数,因为有两个构造函数,既不是默认的零参数构造函数,也没有用 JsonConstructorAttribute 标记。 我的问题真的是“我有什么选择来获得类似的东西?”。这会很棒:
[JsonConstructor]
public record SomethingHappenedEvent(Guid Id, object TheThing, string WhatHappened)
{
public SomethingHappenedEvent(object theThing, string whatHappened)
: this(Guid.NewGuid(), theThing, whatHappened)
{ }
}
但是那试图将属性应用于无效的类型。
这是 C# 中的语法错误,尽管
public record SomethingHappenedEvent
[JsonConstructor]
(Guid Id, object TheThing, string WhatHappened)
{
public SomethingHappenedEvent(object theThing, string whatHappened)
: this(Guid.NewGuid(), theThing, whatHappened)
{ }
}
我目前的解决方案是将这些类型保留为普通 类 并使用所有额外的样板。我也知道我可以省略自定义构造函数并让我的调用者生成 ID。这是可行的,因为 json.net 只能找到一个构造函数。这当然很简洁!但我不喜欢在所有调用站点重复代码,即使在这种情况下它很小。
public record SomethingHappenedEvent(Guid Id, object TheThing, string WhatHappened) { }
FWIW 听起来 System.Text.Json 具有相同的限制。
首先,您只需在创建自己的构造函数时执行此操作。这是因为在实例化时它不知道要使用哪个。
其次,请注意(默认情况下)反序列化器将使用 属性 和构造函数名称,并覆盖您在实际构造函数类型中省略的名称。此外,构造函数中的每个参数都必须在反序列化时绑定到对象 属性 或字段。如果您不注意前者,可能会导致细微的错误,但这不仅限于 records.
除此之外,您的属性放错了地方。简而言之,属性需要在 constructor.
胡乱编造的荒谬示例:
给定
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);
我在试验时运行跨过另一个选项。我会把它留在这里以防它对某人有用。您可以将自定义构造函数转换为工厂方法。这只剩下一个构造函数供反序列化器查找。
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 将填充属性。
如果需要,可以将其设为私有,这样 class 的其他用户 none 就可以错误地使用它。 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)
{ }
}