System.Text.Json 中可以进行多态反序列化吗?
我尝试从 Newtonsoft.Json 迁移到 System.Text.Json。 我想反序列化抽象类。 Newtonsoft.Json 为此提供了 TypeNameHandling。 有没有办法通过 .net core 3.0 上的 System.Text.Json 反序列化抽象类?
I try to migrate from Newtonsoft.Json to System.Text.Json.
I want to deserialize abstract class. Newtonsoft.Json has TypeNameHandling for this.
Is there any way to deserialize abstract class via System.Text.Json on .net core 3.0?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(18)
我最终得到了这个解决方案。它对我来说很轻量级且足够通用。
类型鉴别器转换器
接口
和示例模型
这是测试方法
I ended up with that solution. It's lightwight and a generic enough for me.
The type discriminator converter
The interface
And the example models
And here is the test method
目前,借助 .NET 7 的新功能,我们无需编写方便的代码即可实现此目的。
请参阅此处:https://devblogs.microsoft.com/dotnet/announcing -dotnet-7-preview-5/
我希望这可以帮助你
currently with new feature of .NET 7 we can do this without write handy codes to implement this.
see here: https://devblogs.microsoft.com/dotnet/announcing-dotnet-7-preview-5/
i hope this can help you
请尝试我写的这个库作为System.Text.json提供多态性的扩展:
https://github.com/dahomey-technologies/dahomomey.json.json
如果是实际类型参考实例与已声明的类型不同,必须将歧视属性自动添加到输出JSON:
继承的类:必须手动注册到Incistiminator junterion注册表中,以便让框架知道歧视器值和类型之间的映射:
结果:
Please try this library I wrote as an extension to System.Text.Json to offer polymorphism:
https://github.com/dahomey-technologies/Dahomey.Json
If the actual type of a reference instance differs from the declared type, the discriminator property will be automatically added to the output json:
Inherited classes must be manually registered to the discriminator convention registry in order to let the framework know about the mapping between a discriminator value and a type:
Result:
多数民众赞成在我的所有抽象类型:
Thats my JsonConverter for all abstract types:
基于接受的答案,但是使用newtypeattribute发现类型(通常列举所有类型都会导致不需要的类型加载异常),并在转换器中添加歧视属性,而不是让类实现该类别:
您可以使用这样的使用 。 :
Basing on the accepted answer, but using KnownTypeAttribute to discover the types (often enumerating all types can lead to unwanted type load exceptions) , and adding the discriminator property in the converter instead of having the class implement it itself:
which you can use like this:
我真的很喜欢 demetrius ,但我认为您可以在可重复使用方面走得更远。我提出了以下解决方案:
jsonConverterFactory:
jsonConverter:
disciminatorAttribute:
IndiciminatorValueAttribute:
最后是如何在类上使用它的示例:
和...voilà!
剩下要做的就是注册工厂:
I really liked the answer of Demetrius, but I think you can go even further in terms of re-usability. I came up with the following solution:
The JsonConverterFactory:
The JsonConverter:
The DiscriminatorAttribute:
The DiscriminatorValueAttribute:
And finally, an example of how to use it on classes:
And... Voilà!
All that is left to do is to register the factory:
将此选项扔出去:使用源代码生成器为具有特殊属性标记的属性的对象自动生成JSONCONVERTER,
您可以使用此软件包尝试一下,但是它需要.net5
https://github.com/wivuu/wivuu/wivuu.jsonpolymormorphism
发电机看着具有歧视属性的属性的类型,然后看上去对于从持有歧视器的类型继承的类型,以与枚举源的每种情况相匹配
: https://github.com/wivuu/wivuu.jsonpolymorphism/blob/blob/master/wivuu.jsonpolymorphism/jsonconconvertertertergergenerator.cs
Throwing this option out there: Using a source code generator to generate a JsonConverter automatically for objects with a property marked with a special attribute
You can try it with this package, but it requires .net5
https://github.com/wivuu/Wivuu.JsonPolymorphism
The generator looks at the type of the property marked with a discriminator attribute, and then looks for types inheriting from the type holding the discriminator to match up with each case of the enum
Source here: https://github.com/wivuu/Wivuu.JsonPolymorphism/blob/master/Wivuu.JsonPolymorphism/JsonConverterGenerator.cs
我想引入另一个适合分层、安全、双向、通用用途的实现。
以下警告
$type
然后需要返回阅读器)。示例
转换器
如果您发现了什么,请给我评论。
一些荣誉1。
I want to throw in another implementation suitable for hierarchical, secure, bi-directional, generic usage.
The following caveats
$type
and then would need to go back on the reader).Example
Converter
If you find something, shoot me a comment.
Some kudos to 1.
我根据
我个人喜欢这种方式,因为客户端可以将其对象提供给服务器。
但是,“类型”属性必须首先在对象中。
基类和派生类:
您可以创建
jsonConverter< ibaseclass>
,该> 在序列化时读取和检查“ type”属性。它将使用它来找出应进行的类型。自从我们将第一个属性视为类型以来,必须复制读者。然后,我们必须再次读取完整的对象(将其传递到避免方法)。
客户端现在能够按以下方式发送对象:
编辑:
编辑了读取方法,以便能够处理不在一阶的属性名称。现在,它只是通过JSON读取并停止,直到发现“类型”属性名称
是诚实的,我认为设置此自定义系统的方式。
I changed a couple things based on ahsonkhan's answer.
Personally I like this way since the client can just give their object to the server.
However, the 'Type' property must be first in the object.
Base class and derived classes:
You can create
JsonConverter<IBaseClass>
that reads and checks the 'Type' property while serializing. It will use that to figure out which type to deserialize.The reader has to be copied since we read the first property as the type. And then we have to read the full object again (pass it to the Deserialize method).
The client is now able to send objects as follows:
EDIT:
Edited the Read method to be able to deal with the property name not being in the first order. Now it just reads through the json and stops until it finds the 'Type' property name
To be honest, I think the way this custom System.Text JsonConverter is set up is unneccesary complex and I prefer the Newtonsoft JsonConverter.
请不要这样写,
如果您的课堂包含大本产期属性, 那么您可以像Baseclass一样对他进行审理。
如果您是抽象的,并且包含了基本属性,那么您会得到异常。
这样写是更安全的:
但是,您的基本镜不得包含带有类型的基本或继承器的属性。
Don't write like this
If you class contain baseClass property then you deserialize him like baseClass.
If you baseClass is abstract and contain baseClass property then you got Exception.
It's safer to write like this:
But you BaseClass don't must contain property with type BaseClass or inheritor.
多态性支持作为预览版本(v7) 发布。
https://github.com/dotnet/runtime/issues/63747
Polymorphism support is released as preview versions(v7).
https://github.com/dotnet/runtime/issues/63747
对于接口属性反序列化,我创建了一个简单的 StaticTypeMapConverter
您可以像这样使用它:
For interface property deserialization I've created a simple StaticTypeMapConverter
You can use it like this:
不是很优雅或高效,但可以快速为少量子类型编写代码:
Not very elegant or efficient, but quick to code for a small number of child types:
我喜欢与您分享我使用
System.Text.json
发现的问题。我遵循该方法typediscriminatorConverter
demetrius axenowski> demetrius axenowski 。它运行良好。当我为JSON添加一些注释时,我的问题始于我的问题。例如:
我整天都迷路了,以了解为什么代码不起作用。我创建了一些虚拟代码来了解问题在哪里。现在,所有源代码都在 github 上。
因此,问题是在
jsonpropertyname
我在转换器中检查的属性。例如,这是您可以看到的类,我设置了
jsonpropertyname
,因为我喜欢在较低的情况下查看type
。现在,如果我使用此转换器转换课程:我会收到以下错误:
我删除了
jsonpropertyname
,并且可以正常工作。我试图设置(基本上与变量相同),并且效果很好。因此,不要更改名称。转换器正在两种方式工作(对json和json对象对象)。这是测试代码:
另一个注释与
newtonsoft.json
:我将对象转换为JSON,并且没有任何特定的配置,它很好。当我尝试将结果json转换为对象中时,我会在转换中出现问题。I like to share with you an issue I found using
System.Text.Json
. I followed the approachTypeDiscriminatorConverter
that Demetrius Axenowski. It works very well.My problems started when I added some annotations for the JSON. For example:
I have lost all day to understand why the code didn't work. I created some dummy code to understand where the problem was. All the source code is now on GitHub.
So, the problem was in the
JsonPropertyName
for the property I check in the converter. For example, this is a classAs you can see, I set the
JsonPropertyName
because I like to seetype
in lower case. Now, if I convert the class with this converter:I get the following error:
I removed the
JsonPropertyName
and it works fine. I tried to set(basically, the same as the variable) and it works fine. So, don't change the name. The converter is working both ways (object to Json and Json to object). This is the test code:
Another annotation is related to
Newtonsoft.Json
: I converted the object to Json and it was good without any particular configuration. When I tried to convert the result Json in the object, I got issues in the conversion.如果我正确地说,您想显示派生类对象的所有成员,并具有对基类的对象的引用。请检查此示例是否适合您的需求:
输出将是:
因此,您只需要将基类实例投射为
object
If i got you right, you want to display all members of the object of derived class, having the reference to the object of the base class. Please check this example if that will suit your needs:
The output will be:
So you just need to cast your instance of the base class to
object
最近遇到了这一点,并提出了一个非常简单的转换器。我有一个
creditreport
类,可以根据报告成功完成,失败或仍在待处理,可以返回不同的属性。我不喜欢添加
$ type
属性作为类型歧视器,因为我的模型已经具有一个称为status
的属性,该属性具有相同的目的。但是,由于utf8jsonreader
是唯一的读者,因此使用它是一个挑战。我最终要做的是将JSON解析为jsondocument
,检查status
列,然后对jsondocument
本身进行挑选,将。Recently came across this and came up with a very simple type converter. I have a
CreditReport
class that can return different properties depending on whether the report completed successfully, failed, or is still pending.I didn't love adding a
$type
property as a type discriminator, because my model already has a property calledstatus
that serves the same purpose. It's a challenge to use it though, due toUtf8JsonReader
being a forward only reader. What I ended up doing is parsing the json as aJsonDocument
, checking thestatus
column, and then deserializing theJsonDocument
itself into the correct derived class.答案是“是”和“否”,具体取决于您所说的“可能”的含义。
System.Text.Json没有内置多态反序列化(相当于 Newtonsoft.Json 的
TypeNameHandling
)支持> 在 .NET 7 之前。这是因为不建议读取在 JSON 有效负载中指定为字符串的 .NET 类型名称(例如$type
元数据属性)来创建对象< /强>因为它引入了潜在的安全问题(请参阅 https://github.com/dotnet/ corefx/issues/41347#issuecomment-535779492 了解更多信息)。但是,有一种方法可以通过创建 JsonConverter来添加您自己对多态反序列化的支持,因此从这个意义上来说,这是可能的。
该文档展示了如何使用类型鉴别器属性来做到这一点的示例:
https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to#support-polymorphic-deserialization
让我们看一个例子。
假设您有一个基类和几个派生类:
您可以创建以下
JsonConverter
,它在序列化时写入类型鉴别器并读取它以找出要反序列化的类型。您可以在JsonSerializerOptions
上注册该转换器。这就是序列化和反序列化的样子(包括与 Newtonsoft.Json 的比较):
这是另一个 StackOverflow 问题,它展示了如何使用接口(而不是抽象类)支持多态反序列化,但类似的解决方案适用于任何多态性:
有吗在 System.Text.Json 的自定义转换器中手动序列化/反序列化子对象的简单方法?
The answer is yes and no, depending on what you mean by "possible".
There is no polymorphic deserialization (equivalent to Newtonsoft.Json's
TypeNameHandling
) support built-in toSystem.Text.Json
prior to .NET 7. This is because reading the .NET type name specified as a string within the JSON payload (such as$type
metadata property) to create your objects is not recommended since it introduces potential security concerns (see https://github.com/dotnet/corefx/issues/41347#issuecomment-535779492 for more info).However, there is a way to add your own support for polymorphic deserialization by creating a
JsonConverter<T>
, so in that sense, it is possible.The docs show an example of how to do that using a type discriminator property:
https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to#support-polymorphic-deserialization
Let's look at an example.
Say you have a base class and a couple of derived classes:
You can create the following
JsonConverter<BaseClass>
that writes the type discriminator while serializing and reads it to figure out which type to deserialize. You can register that converter on theJsonSerializerOptions
.This is what serialization and deserialization would look like (including comparison with Newtonsoft.Json):
Here's another StackOverflow question that shows how to support polymorphic deserialization with interfaces (rather than abstract classes), but a similar solution would apply for any polymorphism:
Is there a simple way to manually serialize/deserialize child objects in a custom converter in System.Text.Json?
白名单继承类型的多态序列化已在 .NET 7 中实现,并可在 预览 6。
来自文档页面.NET 7 中 System.Text.Json 的新增功能:类型层次结构:
首先,让我们考虑一下序列化。假设您有以下类型层次结构:
并且您有一个数据模型,其中包含声明类型为
BaseType
的值,例如在以前的版本中,System.Text.Json只会序列化声明类型
BaseType
的属性。现在,通过添加[JsonDerivedType(typeof(TDerivedType))]
序列化声明为BaseType
的值时,您将能够包含DerivedType1
的属性为所有派生类型添加到BaseType
:以这种方式将
DerivedType1
列入白名单,模型的序列化:导致
Demo fiddle #1 此处。
请注意,只有派生类型通过属性(或通过设置
JsonTypeInfo.PolymorphismOptions
在运行时)可以通过此机制进行序列化。如果您有其他未列入白名单的派生类型,例如:那么
JsonSerializer.Serialize(new BaseType [] { new DerivedType3 { Derived3 = "value 3" } })
将抛出System .NotSupportedException:多态类型“BaseType”不支持运行时类型“DerivedType3”
异常。演示小提琴 #2 此处。这涵盖了序列化。如果您需要往返类型层次结构,则需要提供类型鉴别器属性值以用于每个派生类型。这可以通过为
JsonDerivedTypeAttribute 提供值来完成。每个派生类型的 TypeDiscriminator
:现在,当您序列化模型时
System.Text.Json 将添加一个人工类型鉴别器属性
"$type"
指示类型已序列化:完成此操作后,您现在可以像这样反序列化数据模型:
并且序列化的实际具体类型将被保留。演示小提琴 #3 此处。
还可以通过 合同定制。当您的类型层次结构无法修改时,或者某些派生类型位于不同的程序集中并且无法在编译时引用时,或者您尝试在多个旧序列化程序之间进行互操作时,您可能需要执行此操作。这里的基本工作流程是实例化
DefaultJsonTypeInfoResolver
并添加 修饰符,用于设置必要的JsonTypeInfo
作为您的基本类型。例如,可以在运行时启用
BaseType
层次结构的多态序列化,如下所示:Demo fiddle #4 此处。
注意:
白名单方法与数据契约序列化程序的方法一致,后者使用
KnownTypeAttribute
和XmlSerializer
,它使用XmlIncludeAttribute
。它与 Json.NET 不一致,其TypeNameHandling
序列化所有类型的类型信息,除非通过 序列化绑定器。仅允许反序列化白名单类型可防止 13 号星期五:JSON 攻击 类型注入攻击,包括 Newtonsoft Json 中的 TypeNameHandling 小心 和 由于 Json.Net TypeNameHandling auto 导致外部 json 易受攻击?。
整数和字符串都可以用于类型鉴别器名称。如果您按如下方式定义类型层次结构:
然后序列化上面的列表结果
但是 Newtonsoft 不使用数字类型鉴别器值,因此如果您与旧序列化器进行互操作,您可能希望避免这种情况。
默认类型鉴别器属性名称
"$type"
与 Json.NET 使用的类型鉴别器名称相同。如果您希望使用不同的属性名称,例如名称"__type"
由DataContractJsonSerializer
使用,应用JsonPolymorphicAttribute
到基本类型和设置TypeDiscriminatorPropertyName
像这样:如果您与 Json.NET(或 DataContractJsonSerializer)进行互操作,则可以设置
TypeDiscriminator
等于旧序列化器使用的类型鉴别器值。如果序列化程序遇到未列入白名单的派生类型,您可以通过设置
JsonPolymorphicAttribute.UnknownDerivedTypeHandling
为以下之一 值:<块引用>
<表类=“s-表”>
<标题>
JsonUnknownDerivedTypeHandling
值
含义
<正文>
序列化失败
0
未声明运行时类型的对象将无法进行多态序列化。
FallBackToBaseType
1
未声明的运行时类型的对象将回退到基本类型的序列化协定。
回退到最近的祖先
2
未声明的运行时类型的对象将恢复为最近声明的祖先类型的序列化协定。由于钻石模糊性限制,某些接口层次结构不受支持。
Polymorphic serialization of whitelisted inherited types has been implemented in .NET 7, and is available in Preview 6.
From the documentation page What’s new in System.Text.Json in .NET 7: Type Hierarchies:
First, let's consider serialization. Say you have the following type hierarchy:
And you have a data model that includes a value whose declared type is
BaseType
, e.g.In previous versions, System.Text.Json would only serialize the properties of the declared type
BaseType
. Now you will be able to include the properties ofDerivedType1
when serializing a value declared asBaseType
by adding[JsonDerivedType(typeof(TDerivedType))]
toBaseType
for all derived types:Having whitelisted
DerivedType1
in this manner, serialization of your model:Results in
Demo fiddle #1 here.
Do note that only derived types whitelisted via attribute (or through setting
JsonTypeInfo.PolymorphismOptions
in runtime) can be serialized via this mechanism. If you have some other derived type which is not whitelisted, e.g.:Then
JsonSerializer.Serialize(new BaseType [] { new DerivedType3 { Derived3 = "value 3" } })
will throw aSystem.NotSupportedException: Runtime type 'DerivedType3' is not supported by polymorphic type 'BaseType'
exception. Demo fiddle #2 here.That covers serialization. If you need to round-trip your type hierarchy, you will need to supply a type discriminator property value to use for each derived type. This may be done providing a value for
JsonDerivedTypeAttribute.TypeDiscriminator
for each derived type:Now when you serialize your model
System.Text.Json will add an artificial type discriminator property
"$type"
indicating the type that was serialized:Having done so, you can now deserialize your data model like so:
And the actual, concrete type(s) serialized will be preserved. Demo fiddle #3 here.
It is also possible to inform System.Text.Json of your type hierarchy in runtime via Contract Customization. You might need to do this when your type hierarchy cannot be modified, or when some derived types are in different assemblies and cannot be referenced at compile time, or you are trying to interoperate between multiple legacy serializers. The basic workflow here will be to instantiate an instance of
DefaultJsonTypeInfoResolver
and add a modifier which sets up the necessaryPolymorphismOptions
for theJsonTypeInfo
for your base type.For example, polymorphic serialization for the
BaseType
hierarchy can be enabled in runtime like so:Demo fiddle #4 here.
Notes:
The whitelisting approach is consistent with the approach of the data contract serializers, which use the
KnownTypeAttribute
, andXmlSerializer
, which usesXmlIncludeAttribute
. It is inconsistent with Json.NET, whoseTypeNameHandling
serializes type information for all types unless explicitly filtered via a serialization binder.Allowing only whitelisted types to be deserialized prevents Friday the 13th: JSON Attacks type injection attacks including those detailed in TypeNameHandling caution in Newtonsoft Json and External json vulnerable because of Json.Net TypeNameHandling auto?.
Integers as well as strings may be used for the type discriminator name. If you define your type hierarchy as follows:
Then serializing the list above results in
Numeric type discriminator values are not used by Newtonsoft however, so if you are interoperating with a legacy serializer you might want to avoid this.
The default type discriminator property name,
"$type"
, is the same type discriminator name used by Json.NET. If you would prefer to use a different property name, such as the name"__type"
used byDataContractJsonSerializer
, applyJsonPolymorphicAttribute
to the base type and setTypeDiscriminatorPropertyName
like so:If you are interoperating with Json.NET (or DataContractJsonSerializer), you may set the value of
TypeDiscriminator
equal to the type discriminator value used by the legacy serializer.If the serializer encounters a derived type that has not been whitelisted, you can control its behavior by setting
JsonPolymorphicAttribute.UnknownDerivedTypeHandling
to one of the following values: