(De)将具有基于接口的多态属性的类型序列化为JSON或从JSON序列化



首先,我对以下代码的长度表示歉意;它几乎是我能得到的最小值。

我正在尝试(反)序列化十几条在第三方库中定义的消息;我不能以任何方式修改这些类和接口。这些消息都是基于接口的;不幸的是,它们没有共享基类,只有IMessage<T>接口。以下是信息;我在IMessage<T>等中省略了不相关的属性,如Header。出于演示目的,我提供了每种消息的两种"类型",FooBar

// 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; }
}

由于无法修改以上类型,因此无法使用[JsonDerivedType]属性等。我希望用契约模型配置多态性将是解决方案,但可能不是。我更喜欢使用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()
};
// Serialize a message
var json = JsonSerializer.Serialize(message1.Body.Content, opt);
Console.WriteLine(json);
// Deserialize message
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;
}
}

上述代码有效;在这个网络游戏中可以看到它的作用。正如您所看到的,类型鉴别器"$type": "Foo"被正确地添加到JSON中。

然而,当我取消对new JsonDerivedType(typeof(Bar), nameof(Bar)),行的注释时,问题就开始了;这导致以下异常:

System.InvalidOperationException:指定的类型"Bar"不是多态类型"Foo"支持的派生类型。派生类型必须可分配给基类型,不能是泛型,并且不能是抽象类或接口,除非指定了"JsonUnknownDriverTypeHandling.FallBackToNearestAncestor">

我已经尝试了很多变体,但我无法为我的生活正确地(反)序列化。任何正确方向的帮助或指示都将不胜感激。目标是能够在上面给定的代码中(反)序列化message1message2。我并没有下定决心要使用JsonTypeInfoResolver路线;实现这一目标的任何其他方式(这对十几种,未来可能更多的消息类型来说都是可以管理的)都是受欢迎的。

您的问题在这里:

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,然后添加派生类型FooBar。但是IMessageContent有许多派生类型,包括Foo本身。然后,当您的代码被调用时,它实际上是在尝试将派生类型Bar分配给Foo,但失败了,因为Bar不是Foo的派生类型。

要解决这个问题,在添加之前,您应该检查每个传入的派生类型是否可以实际分配给JsonTypeInfoInfo.Type

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继承,因为修饰符可以更容易地组合和重用

在这里演示小提琴。

您不能创建通用自定义序列化程序,甚至不能创建通用方法,因为所有通用接口都只有{get;}(只读)。只有混凝土碎屑才有设置器。因此,最简单的方法是将这样的代码用于每个第三方类

FooMessage fooMessage = new FooMessage { 
Body = JsonObject.Parse(json)["Body"].Deserialize<FooBody>()};

相关内容

  • 没有找到相关文章

最新更新