英文:
(De)Serializing type with interface-based polymorphic properties into/from JSON
问题
我将只翻译您提供的代码部分,不包括问题本身。
// 首先,我为下面的代码长度道歉;它是我能使它尽可能简化的。
// 我正在尝试(de)序列化约十多条消息,这些消息在第三方库中定义;我不能以任何方式修改这些类和接口。不幸的是,这些消息都是基于接口的;它们不共享基类,只有 `IMessage<T>` 接口。以下是这些消息;我省略了不相关的属性,如 `IMessage<T>` 上的 `Header` 等。为演示目的,我提供了每种消息 '类型' 的两个示例,一个 `Foo` 和一个 `Bar`。
// 由于我不能修改上述类型,所以不能使用 `[JsonDerivedType]` 属性等。我希望 [使用合同模型配置多态性](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/polymorphism?pivots=dotnet-7-0#configure-polymorphism-with-the-contract-model) 将是解决方案 - 但这可能不是。我更喜欢使用 `System.Text.Json` - 尽管如果绝对必要,我会考虑 `NewtonSoft.Json`。
// 所以这是我尝试做的事情:
// 创建消息
var message1 = new FooMessage { Body = new FooBody { Content = new Foo { MessageId = "abc", FooValue = "foo" } } };
//var message2 = new BarMessage { Body = new BarBody { Content = new Bar { MessageId = "xyz", BarValue = "bar" } } };
var opt = new JsonSerializerOptions
{
TypeInfoResolver = new MyMessageTypeResolver()
};
// 序列化消息
var json = JsonSerializer.Serialize(message1.Body.Content, opt);
Console.WriteLine(json);
// 反序列化消息
var obj = JsonSerializer.Deserialize<IMessageContent>(json, opt);
// 为此目的,我实现了一个 `MyMessageTypeResolver`,该解析器传递给了序列化程序的 `Serialize(...)` 和 `Deserialize(...)` 方法。
public class MyMessageTypeResolver : DefaultJsonTypeInfoResolver
{
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
{
var jsonTypeInfo = base.GetTypeInfo(type, options);
if (typeof(IMessageContent).IsAssignableFrom(jsonTypeInfo.Type))
{
jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions
{
TypeDiscriminatorPropertyName = "$type",
IgnoreUnrecognizedTypeDiscriminators = true,
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization,
DerivedTypes =
{
new JsonDerivedType(typeof(Foo), nameof(Foo)),
//new JsonDerivedType(typeof(Bar), nameof(Bar)),
}
};
}
return jsonTypeInfo;
}
}
上面的代码有效;可以在 这个 dotnetfiddle 中看到它正在运行。正如您所见,JSON 中正确地添加了 类型判别器 $type: Foo
。
然而,当取消注释 new JsonDerivedType(typeof(Bar), nameof(Bar)),
行时,会出现以下异常:
System.InvalidOperationException: 指定的类型 'Bar' 不是多态类型 'Foo' 支持的派生类型。派生类型必须能够赋值给基类型,不能是泛型,并且不能是抽象类或接口,除非指定了 'JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor'。
我尝试了很多变化,但我无法正确(de)序列化。如果有帮助或指向正确方向的建议,将不胜感激。目标是能够在上述给出的代码中(de)序列化 message1
和 message2
。我不一定要使用 JsonTypeInfoResolver
路线;欢迎任何其他方式(对于未来可能有更多的消息类型来说,这是可行的)来实现这个目标。
英文:
First of all, I apologize for the length of below code; it's about as minimal as I could get it.
I'm trying to (de)serialize a dozen or so messages that are defined in a 3rd party library; I cannot modify these classes and interfaces in any way. The messages are all interface based; they share no baseclass unfortunately, just the IMessage<T>
interface. Below are the messages; I've left out irrelevant properties like Header
on an IMessage<T>
etc. I've provided two of each message 'types', a Foo
and a Bar
, for demonstration purposes.
// Below types are defined by a 3rd party and cannot be modified
public interface IMessage<out T> where T : IMessageContent
{
IMessageBody<T> Body { get; }
}
public interface IMessageBody<out T> where T : IMessageContent
{
T Content { get; }
}
public interface IMessageContent {
string MessageId { get; set; }
}
public class FooMessage : IMessage<Foo>
{
public IMessageBody<Foo> Body { get; set; }
}
public class BarMessage : IMessage<Bar>
{
public IMessageBody<Bar> Body { get; set; }
}
public class FooBody : IMessageBody<Foo>
{
public Foo Content { get; set; }
}
public class BarBody : IMessageBody<Bar>
{
public Bar Content { get; set; }
}
public class Foo : IMessageContent
{
public string MessageId { get; set; }
public string FooValue { get; set; }
}
public class Bar : IMessageContent
{
public string MessageId { get; set; }
public string BarValue { get; set; }
}
Since I cannot modify the above types, I cannot use [JsonDerivedType]
attributes and such. I'm hoping Configuring polymorphism with the contract model will be the solution - but it may not be. I prefer to use System.Text.Json
- though if absolutely necessary I will consider NewtonSoft.Json
.
So here's what I'm trying to do:
var message1 = new FooMessage { Body = new FooBody { Content = new Foo { MessageId = "abc", FooValue = "foo" } } };
//var message2 = new BarMessage { Body = new BarBody { Content = new Bar { MessageId = "xyz", BarValue = "bar" } } };
var opt = new JsonSerializerOptions
{
TypeInfoResolver = new MyMessageTypeResolver()
};
// Serialize a message
var json = JsonSerializer.Serialize(message1.Body.Content, opt);
Console.WriteLine(json);
// Deserialize message
var obj = JsonSerializer.Deserialize<IMessageContent>(json, opt);
For this purpose I have implemented a MyMessageTypeResolver
which is passed into the serializer's Serialize(...)
and Deserialize(...)
methods.
public class MyMessageTypeResolver : DefaultJsonTypeInfoResolver
{
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
{
var jsonTypeInfo = base.GetTypeInfo(type, options);
if (typeof(IMessageContent).IsAssignableFrom(jsonTypeInfo.Type))
{
jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions
{
TypeDiscriminatorPropertyName = "$type",
IgnoreUnrecognizedTypeDiscriminators = true,
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization,
DerivedTypes =
{
new JsonDerivedType(typeof(Foo), nameof(Foo)),
//new JsonDerivedType(typeof(Bar), nameof(Bar)),
}
};
}
return jsonTypeInfo;
}
}
The above code works; it can be seen in action in this dotnetfiddle. As you can see, a type discriminator "$type": "Foo"
is correctly added to the JSON.
However, the problem begins when I uncomment the new JsonDerivedType(typeof(Bar), nameof(Bar)),
line; this results in the following exception:
System.InvalidOperationException: Specified type 'Bar' is not a supported derived type for the polymorphic type 'Foo'. Derived types must be assignable to the base type, must not be generic and cannot be abstract classes or interfaces unless 'JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor' is specified.
I've tried a lot of variations, but I can't get this to properly (de)serialize for the life of me. Any help or pointers in the right direction are much appreciated. The goal is to be able to (de)serialize message1
and message2
in above given code. I'm not dead set on using the JsonTypeInfoResolver
route; any other way (that's manageable for a dozen, maybe more in the future, message types) that achieves this goal is welcome.
答案1
得分: 1
你的问题在这里:
if (typeof(IMessageContent).IsAssignableFrom(jsonTypeInfo.Type))
{
// Intermediate code snipped
DerivedTypes =
{
new JsonDerivedType(typeof(Foo), nameof(Foo)),
new JsonDerivedType(typeof(Bar), nameof(Bar)),
}
}
你检查了jsonTypeInfo.Type
是否可以分配给IMessageContent
,然后添加了派生类型Foo
和Bar
。但是,IMessageContent
有许多派生类型,包括Foo
本身。然后,当你的代码被调用时,实际上是尝试将派生类型Bar
分配给Foo
,这会失败,因为Bar
不是Foo
的派生类型。
为了解决这个问题,你应该在添加之前检查每个传入的派生类型是否实际上可以分配给JsonTypeInfoInfo.Type
。这里是一个可以实现这个功能的typeInfo修饰符:
public class JsonExtensions
{
public static Action<JsonTypeInfo> AddDerivedTypes(Type baseType, IEnumerable<Type> derivedTypes) => typeInfo =>
{
if (typeInfo.Kind != JsonTypeInfoKind.Object)
return;
if (baseType.IsAssignableFrom(typeInfo.Type))
{
var applicableDerivedTypes = derivedTypes
// Check to make sure the current derived type is actually assignable to typeInfo.Type (which might also be a derived type of baseType)
.Where(t => typeInfo.Type.IsAssignableFrom(t));
bool first = true;
foreach (var type in applicableDerivedTypes)
{
if (first)
// TODO: decide what to do if typeInfo.PolymorphismOptions is not null (because e.g. it was initialized via attributes).
typeInfo.PolymorphismOptions = new JsonPolymorphismOptions
{
TypeDiscriminatorPropertyName = "$type",
IgnoreUnrecognizedTypeDiscriminators = true,
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization,
};
first = false;
typeInfo.PolymorphismOptions!.DerivedTypes.Add(new JsonDerivedType(type, type.Name));
}
}
};
}
然后,你可以像这样使用修饰符:
var opt = new JsonSerializerOptions
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers = { JsonExtensions.AddDerivedTypes(typeof(IMessageContent), new [] { typeof(Foo), typeof(Bar) }) },
}
};
注意:
- 我建议通过修饰符来自定义System.Text.Json合同,而不是从
DefaultJsonTypeInfoResolver
继承,因为修饰符更容易组合和重用。
演示示例在此处。
英文:
Your problem is here:
if (typeof(IMessageContent).IsAssignableFrom(jsonTypeInfo.Type))
{
// Intermediate code snipped
DerivedTypes =
{
new JsonDerivedType(typeof(Foo), nameof(Foo)),
new JsonDerivedType(typeof(Bar), nameof(Bar)),
}
You check to see whether jsonTypeInfo.Type
is assignable to IMessageContent
, and then add derived types Foo
and Bar
. But IMessageContent
has many derived types, including Foo
itself. Then when your code is invoked, it is actually attempting to assign the derived type Bar
to Foo
, which fails, because Bar
is not a derived type of Foo
.
To fix this, you should check that each incoming derived type is actually assignable to JsonTypeInfoInfo.Type
before adding it. Here is a typeInfo modifier that does that:
public class JsonExtensions
{
public static Action<JsonTypeInfo> AddDerivedTypes(Type baseType, IEnumerable<Type> derivedTypes) => typeInfo =>
{
if (typeInfo.Kind != JsonTypeInfoKind.Object)
return;
if (baseType.IsAssignableFrom(typeInfo.Type))
{
var applicableDerivedTypes = derivedTypes
// Check to make sure the current derived type is actually assignable to typeInfo.Type (which might also be a derived type of baseType)
.Where(t => typeInfo.Type.IsAssignableFrom(t));
bool first = true;
foreach (var type in applicableDerivedTypes)
{
if (first)
// TODO: decide what to do if typeInfo.PolymorphismOptions is not null (because e.g. it was initialized via attributes).
typeInfo.PolymorphismOptions = new JsonPolymorphismOptions
{
TypeDiscriminatorPropertyName = "$type",
IgnoreUnrecognizedTypeDiscriminators = true,
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization,
};
first = false;
typeInfo.PolymorphismOptions!.DerivedTypes.Add(new JsonDerivedType(type, type.Name));
}
}
};
}
You would then use the modifier as follows:
var opt = new JsonSerializerOptions
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers = { JsonExtensions.AddDerivedTypes(typeof(IMessageContent), new [] { typeof(Foo), typeof(Bar) }) },
}
};
Notes:
- I recommend customizing System.Text.Json contracts via modifiers rather than by inheriting from
DefaultJsonTypeInfoResolver
because modifiers can more easily be combined and reused.
Demo fiddle here.
答案2
得分: -1
以下是已翻译的内容:
你不能创建一个通用的自定义序列化程序,甚至不能创建一个通用的方法,因为你的所有通用接口只有 {get;}(只读)。只有具体的类才有设置器。所以最简单的方法是对你的第三方类的每个类使用如下代码:
FooMessage fooMessage = new FooMessage {
Body = JsonObject.Parse(json)["Body"].Deserialize<FooBody>()};
英文:
You can not create a generic custom serializer, not even a generic method because all your generic interfaces have only {get;} (read only). Only concrete clases have setters. So the easiest way is to use the code like this for each of your third party classes
FooMessage fooMessage = new FooMessage {
Body = JsonObject.Parse(json)["Body"].Deserialize<FooBody>()};
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论