Json.net如何序列化/反序列化派生类型?

138

json.net(newtonsoft)
我正在查阅文档,但找不到关于此事的任何信息或最佳方法。

public class Base
{
    public string Name;
}
public class Derived : Base
{
    public string Something;
}

JsonConvert.Deserialize<List<Base>>(text);

现在我有一个序列化列表中的派生对象。如何反序列化该列表并获取回派生类型?


那不是继承的工作方式。您可以指定JsonConvert.Deserialize<Derived>(text);以包括Name字段。由于Derived IS A Base(而不是反过来),Base不知道Derived的定义。 - M.Babcock
1
抱歉,我稍微澄清一下。问题是我有一个包含基类和派生类对象的列表。因此,我需要找出如何告诉newtonsoft如何反序列化派生项。 - Will
你解决了吗?我也遇到了同样的问题。 - Luis Carlos Chavarría
6个回答

129

您需要启用类型名称处理,并将其作为设置参数传递给序列化和反序列化操作。

Base object1 = new Base() { Name = "Object1" };
Derived object2 = new Derived() { Something = "Some other thing" };
List<Base> inheritanceList = new List<Base>() { object1, object2 };

JsonSerializerSettings settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
string Serialized = JsonConvert.SerializeObject(inheritanceList, settings);
List<Base> deserializedList = JsonConvert.DeserializeObject<List<Base>>(Serialized, settings);

这将导致派生类的正确反序列化。它的缺点是它会为您使用的所有对象命名,因此它将为您放置对象的列表命名。


34
我Google了30分钟才发现,在使用SerializeObject和DeserializeObject时需要使用相同的设置。我以为如果在反序列化时有$type,它会自动使用它,真是太傻了。 - Erti-Chris Eelmaa
31
TypeNameHandling.Auto也可以实现此功能,并且更好,因为它在字段/属性类型与实例类型匹配时不会写入实例类型名称,这通常是大多数字段/属性的情况。 - Roman Starkov
5
当在另一个解决方案/项目上执行反序列化时,此方法无法正常工作。在序列化过程中,Solution的名称被嵌入类型中:"SOLUTIONNAME.Models.Model"。当在其他解决方案上进行反序列化时,它会抛出异常:"JsonSerializationException: Could not load assembly 'SOLUTIONNAME'。 - Jebathon
2
我从没想到能这么容易!我在谷歌中翻遍了所有内容,几乎拔光头发都要试图找出如何编写自定义序列化器/反序列化器!最后我终于找到了这个答案,并最终解决了问题!你是我的救星!非常感谢! - Noob001
@Jebathon,你在两个解决方案中都有至少一个同名的项目和相同命名空间的类。但最好共享同一个库。 - Michael Freidgeim

58
如果您在您的文本中存储类型(在这种情况下应该这样做),您可以使用JsonSerializerSettings。
参见:如何使用Newtonsoft JSON.NET将JSON反序列化为IEnumerable。
但是要小心。除了TypeNameHandling = TypeNameHandling.None之外的任何其他用法都可能使您面临安全漏洞 - 请参阅“如何配置Json.NET以创建易受攻击的Web API”。

31
您也可以使用 TypeNameHandling = TypeNameHandling.Auto - 这将仅在声明的类型(例如 Base)与实例类型(例如 Derived)不匹配的情况下,为实例添加 $type 属性。这样,它不会像 TypeNameHandling.All 那样让您的 JSON 变得臃肿。 - AJ Richardson
我一直收到错误信息:解析JSON中指定的类型“...,...”时出错。路径“$type”,行1,位置82。有什么想法吗? - briba
6
在公共端点使用此功能时要小心,因为它会导致安全问题:https://www.alphabot.com/security/blog/2017/net/How-to-configure-Json.NET-to-create-a-vulnerable-web-API.html - gjvdkamp
2
@gjvdkamp 天啊,谢谢你分享这个信息,我之前不知道。我会在我的帖子中加入这个内容。 - kamranicus
对于那些正在寻找的人,这是如何使用System.Text.Json.Serialization完成相同操作的方法:https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-polymorphism - ryanwebjackson
1
6个月后... 我强烈反对使用这种方法。除了安全问题之外,这还会使重命名类或将它们移动到其他命名空间变得非常麻烦。重构将变成一场噩梦。 现在,我使用自定义的“鉴别器字段”来确定要将子类反序列化为什么对象。基本上是相同的想法,但它不再与类命名空间绑定... - Eyad Arafat

36

鉴于这个问题非常受欢迎,如果您想控制类型属性名称及其值,添加以下内容可能会很有用。

一种冗长的方式是编写自定义JsonConverter来通过手动检查和设置类型属性来处理(反)序列化。

更简单的方法是使用JsonSubTypes,它通过属性处理所有样板文件:

[JsonConverter(typeof(JsonSubtypes), "Sound")]
[JsonSubtypes.KnownSubType(typeof(Dog), "Bark")]
[JsonSubtypes.KnownSubType(typeof(Cat), "Meow")]
public class Animal
{
    public virtual string Sound { get; }
    public string Color { get; set; }
}

public class Dog : Animal
{
    public override string Sound { get; } = "Bark";
    public string Breed { get; set; }
}

public class Cat : Animal
{
    public override string Sound { get; } = "Meow";
    public bool Declawed { get; set; }
}

8
我理解你的需求,但我不喜欢让基类意识到所有“已知的子类型”…… - Matt Knowles
3
如果您查看文档,还有其他选项可供选择。我只提供了我更喜欢的示例。 - rzippo
2
这是更安全的方法,不会在反序列化时暴露您的服务以加载任意类型。 - David Burg
2
JsonSubtypes 定义在哪里?我使用的是 Newtonsoft.Json 版本 12.0.0.0,并且没有提到那篇文章中的 JsonSubtypesJsonSubTypesJsonSubtypesConverterBuilder - Matt Arnold
1
@MattArnold 这是一个单独的 Nuget 包。 - rzippo
显示剩余3条评论

9
使用 JsonKnownTypes,它提供了一种类似的使用方式,并向 json 添加了鉴别器。
[JsonConverter(typeof(JsonKnownTypeConverter<BaseClass>))]
[JsonKnownType(typeof(Base), "base")]
[JsonKnownType(typeof(Derived), "derived")]
public class Base
{
    public string Name;
}
public class Derived : Base
{
    public string Something;
}

现在,当您将对象序列化为 json 时,它将添加 "$type",其中包含 "base" 和 "derived" 值,并且它将用于反序列化。
序列化列表示例:
[
    {"Name":"some name", "$type":"base"},
    {"Name":"some name", "Something":"something", "$type":"derived"}
]

0
在.NET 6/7中,你可以使用
[JsonDerivedType(typeof(Panel), typeDiscriminator: "panel")]

查看此链接

这段代码应该放在哪里?请还附上链接的摘录。链接可能在将来失效,你的回答也会变得无用。 - sanitizedUser
我知道这个已经有点过时了,但根据那个链接,JsonDerivedType只在.NET 7/8中可用。 - undefined
你是在建议使用System.Text.Json实现吗?这在另一个问题中有描述is-polymorphic-deserialization-possible-in-system-text-json。这个问题是关于Newtonsoft Json.Net的。 - undefined

-1

只需在 Serialize 方法中添加对象即可

 var jsonMessageBody = JsonSerializer.Serialize<object>(model);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接