我设法通过使用 JsonConvert 类和 [JsonConverter] 属性来完成这项工作。这样ConfigureServices()方法就不需要配置了。
-
在我的 .Application.Contracts 项目中添加了输入和输出 DTO,并在 BASE CLASSES 上使用 [JsonConverter(typeof(MyConverterClass))] 属性进行了修饰(将此属性添加到子类将导致序列化程序中出现循环好像。)
-
添加了一个覆盖基类的枚举属性,从而表示派生类类型,用作鉴别器
-
在以下行中创建了一个适当的转换器类(与 DTO 在同一项目中)
DTO 类:
[JsonConvert(typeof(AnimalInputJsonConverter))]
public abstract class AnimalInputDto : EntityDto<Guid>
{
public string Name { get; set; }
public virtual AnimalType AnimalType => AnimalType.NotSelected
}
public class CatInputDto : AnimalInputDto
{
public override AnimalType AnimalType => AnimalType.Cat
[.. more properties specific to Cat]
}
[JsonConvert(typeof(AnimalOutputJsonConverter))]
public abstract class AnimalOutputDto : EntityDto<Guid>
{
public string Name { get; set; }
public virtual AnimalType AnimalType => AnimalType.NotSelected
}
public class CatOutputDto : AnimalOutputDto
{
public override AnimalType AnimalType => AnimalType.Cat
[.. more properties specific to Cat]
}
转换器示例(输入和输出 DTO 的代码基本相同)
public class AnimalInputDtoJsonConverter : JsonConverter<AnimalInputDto>
{
public override bool CanConvert(Type typeToConvert) =>
typeof(AnimalInputDto).IsAssignableFrom(typeToConvert);
public override AnimalInputDto Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// Take a copy of the reader as we need to check through the object first before deserializing.
Utf8JsonReader readerClone = reader;
if (readerClone.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}
AnimalType typeDiscriminator = AnimalType.NotSelected;
string camelCasedPropertyName =
nameof(AnimalDto.AnimalType).ToCamelCase();
// Loop through the JSON tokens. Look for the required property by name.
while (readerClone.Read())
{
if (readerClone.TokenType == JsonTokenType.PropertyName && readerClone.GetString() == camelCasedPropertyName)
{
// Move on to the value, which has to parse out to an enum
readerClone.Read();
if (readerClone.TokenType == JsonTokenType.Number)
{
int value = readerClone.GetInt32();
try
{
typeDiscriminator = (AnimalType)value;
break;
}
catch (InvalidCastException)
{
throw new JsonException($"{value} is not a recognised integer representation of {typeof(AnimalType)}");
}
}
}
}
AnimalInputDto target = typeDiscriminator switch
{
AnimalType.Cat => JsonSerializer.Deserialize<CatInputDto>(ref reader, options),
_ => throw new NotSupportedException($"The supplied object is not a recognised derivative of {typeof(AnimalInputDto)}")
};
return target;
}
public override void Write(
Utf8JsonWriter writer,
AnimalInputDto value,
JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
}
此外,通用方法似乎是可行的,尽管此代码未经过优化或性能测试,但我预计使用反射和使用 Activator.CreateInstance() 来检查其鉴别器的值的对象实例化会降低性能。
请注意,下面假设鉴别器属性是一个枚举,并且派生类的这个属性的名称与枚举类型完全相同:
如下使用:
[JsonConvert(typeof(PolymorphicJsonConverter<AnimalInputDto, AnimalType>))]
public abstract class AnimalInputDto : EntityDto<Guid>
{
public string Name { get; set; }
public virtual AnimalType AnimalType => AnimalType.NotSelected
}
...
public class PolymorphicJsonConverter<T, U> : JsonConverter<T>
where T : EntityDto<Guid>
where U : Enum
{
public string TypeDiscriminator { get; private set; }
public string TypeDiscriminatorCamelCase { get; private set; }
public List<Type> DerivableTypes { get; private set; }
public PolymorphicJsonConverter()
: base()
{
TypeDiscriminator = typeof(U).Name;
TypeDiscriminatorCamelCase = TypeDiscriminator.ToCamelCase();
DerivableTypes = new List<Type>();
foreach (var domainAssembly in AppDomain.CurrentDomain.GetAssemblies())
{
var assemblyTypes = domainAssembly.GetTypes()
.Where(type => type.IsSubclassOf(typeof(T)) && !type.IsAbstract);
DerivableTypes.AddRange(assemblyTypes);
}
}
public override bool CanConvert(Type typeToConvert) =>
typeof(T).IsAssignableFrom(typeToConvert);
public override T Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// Take a copy of the reader as we need to check through the object first before deserializing.
Utf8JsonReader readerClone = reader;
if (readerClone.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}
U typeDiscriminatorValue = (U)Enum.ToObject(typeof(U), 0);
// Loop through the JSON tokens. Look for the required property by name.
while (readerClone.Read())
{
if (readerClone.TokenType == JsonTokenType.PropertyName && readerClone.GetString() == TypeDiscriminatorCamelCase)
{
// Move on to the value, which has to parse out to an enum
readerClone.Read();
if (readerClone.TokenType == JsonTokenType.Number)
{
int value = readerClone.GetInt32();
try
{
typeDiscriminatorValue = (U)Enum.ToObject(typeof(U), value);
break;
}
catch (InvalidCastException)
{
throw new NotSupportedException($"{value} is not a recognised integer representation of {typeof(U)}");
}
}
}
}
T target = null;
foreach(var dt in DerivableTypes)
{
var newInst = Activator.CreateInstance(dt);
var propValue = (U)newInst.GetType().GetProperty(TypeDiscriminator).GetValue(newInst, null);
if (propValue.Equals(typeDiscriminatorValue))
{
target = (T)JsonSerializer.Deserialize(ref reader, dt, options);
}
}
if (target == null)
{
throw new NotSupportedException($"The supplied object is not a recognised derivative of {typeof(T)}");
}
return target;
}
public override void Write(
Utf8JsonWriter writer,
T value,
JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
}
上述灵感/进一步阅读:
https://getyourbitstogether.com/polymorphic-serialization-in-system-text-json/
https://vpaulino.wordpress.com/2021/02/23/deserializing-polymorphic-types-with-system-text-json/
https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to?pivots=dotnet-6-0
https://docs.microsoft.com/en-us/dotnet/api/system.text.json.serialization.jsonconverter-1?view=net-6.0
https://docs.microsoft.com/en-us/dotnet/api/system.text.json.serialization.jsonconverterattribute?view=net-6.0