我尝试从Newtonsoft.Json迁移到System.Text.Json。 我想要反序列化抽象类。Newtonsoft.Json可以使用TypeNameHandling来实现。 在.NET Core 3.0上,是否有方法可以通过System.Text.Json反序列化抽象类?
我尝试从Newtonsoft.Json迁移到System.Text.Json。 我想要反序列化抽象类。Newtonsoft.Json可以使用TypeNameHandling来实现。 在.NET Core 3.0上,是否有方法可以通过System.Text.Json反序列化抽象类?
System.Text.Json内置不支持多态反序列化(相当于Newtonsoft.Json的TypeNameHandling)。这是因为在JSON有效负载中读取指定为字符串的.NET类型名称(如$type元数据属性)以创建对象不被建议,因为它会引入潜在的安全问题(有关更多信息,请参见https://github.com/dotnet/corefx/issues/41347#issuecomment-535779492)。
允许负载指定其自己的类型信息是Web应用程序漏洞的常见来源。
然而,通过创建JsonConverter<T>
,你可以添加你自己的多态反序列化支持,所以从这个意义上说,是可行的。
文档展示了如何使用类型鉴别器属性进行操作的示例:https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to#support-polymorphic-deserialization
让我们看一个例子。
假设你有一个基类和几个派生类:
public class BaseClass
{
public int Int { get; set; }
}
public class DerivedA : BaseClass
{
public string Str { get; set; }
}
public class DerivedB : BaseClass
{
public bool Bool { get; set; }
}
您可以创建以下 JsonConverter<BaseClass>
,在序列化时编写类型鉴别器并读取它以确定要反序列化的类型。 您可以在JsonSerializerOptions
上注册该转换器。
public class BaseClassConverter : JsonConverter<BaseClass>
{
private enum TypeDiscriminator
{
BaseClass = 0,
DerivedA = 1,
DerivedB = 2
}
public override bool CanConvert(Type type)
{
return typeof(BaseClass).IsAssignableFrom(type);
}
public override BaseClass Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}
if (!reader.Read()
|| reader.TokenType != JsonTokenType.PropertyName
|| reader.GetString() != "TypeDiscriminator")
{
throw new JsonException();
}
if (!reader.Read() || reader.TokenType != JsonTokenType.Number)
{
throw new JsonException();
}
BaseClass baseClass;
TypeDiscriminator typeDiscriminator = (TypeDiscriminator)reader.GetInt32();
switch (typeDiscriminator)
{
case TypeDiscriminator.DerivedA:
if (!reader.Read() || reader.GetString() != "TypeValue")
{
throw new JsonException();
}
if (!reader.Read() || reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}
baseClass = (DerivedA)JsonSerializer.Deserialize(ref reader, typeof(DerivedA));
break;
case TypeDiscriminator.DerivedB:
if (!reader.Read() || reader.GetString() != "TypeValue")
{
throw new JsonException();
}
if (!reader.Read() || reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}
baseClass = (DerivedB)JsonSerializer.Deserialize(ref reader, typeof(DerivedB));
break;
default:
throw new NotSupportedException();
}
if (!reader.Read() || reader.TokenType != JsonTokenType.EndObject)
{
throw new JsonException();
}
return baseClass;
}
public override void Write(
Utf8JsonWriter writer,
BaseClass value,
JsonSerializerOptions options)
{
writer.WriteStartObject();
if (value is DerivedA derivedA)
{
writer.WriteNumber("TypeDiscriminator", (int)TypeDiscriminator.DerivedA);
writer.WritePropertyName("TypeValue");
JsonSerializer.Serialize(writer, derivedA);
}
else if (value is DerivedB derivedB)
{
writer.WriteNumber("TypeDiscriminator", (int)TypeDiscriminator.DerivedB);
writer.WritePropertyName("TypeValue");
JsonSerializer.Serialize(writer, derivedB);
}
else
{
throw new NotSupportedException();
}
writer.WriteEndObject();
}
}
以下展示了序列化和反序列化的示例(包括与Newtonsoft.Json的比较):
private static void PolymorphicSupportComparison()
{
var objects = new List<BaseClass> { new DerivedA(), new DerivedB() };
// Using: System.Text.Json
var options = new JsonSerializerOptions
{
Converters = { new BaseClassConverter() },
WriteIndented = true
};
string jsonString = JsonSerializer.Serialize(objects, options);
Console.WriteLine(jsonString);
/*
[
{
"TypeDiscriminator": 1,
"TypeValue": {
"Str": null,
"Int": 0
}
},
{
"TypeDiscriminator": 2,
"TypeValue": {
"Bool": false,
"Int": 0
}
}
]
*/
var roundTrip = JsonSerializer.Deserialize<List<BaseClass>>(jsonString, options);
// Using: Newtonsoft.Json
var settings = new Newtonsoft.Json.JsonSerializerSettings
{
TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Objects,
Formatting = Newtonsoft.Json.Formatting.Indented
};
jsonString = Newtonsoft.Json.JsonConvert.SerializeObject(objects, settings);
Console.WriteLine(jsonString);
/*
[
{
"$type": "PolymorphicSerialization.DerivedA, PolymorphicSerialization",
"Str": null,
"Int": 0
},
{
"$type": "PolymorphicSerialization.DerivedB, PolymorphicSerialization",
"Bool": false,
"Int": 0
}
]
*/
var originalList = JsonConvert.DeserializeObject<List<BaseClass>>(jsonString, settings);
Debug.Assert(originalList[0].GetType() == roundTrip[0].GetType());
}
这里有一个StackOverflow的问题,展示了如何使用接口(而不是抽象类)来支持多态反序列化,但类似的解决方案也适用于任何多态性: 在 System.Text.Json 中自定义转换器时,是否有一种简单的方法手动序列化/反序列化子对象?
JsonSerializerOptions
传递给 JsonSerializer.Serialize
和 JsonSerializer.Deserialize
的调用。 - V0ldek.NET 7已经实现了继承白名单多态序列化,该功能在预览版6中可用。
从文档页.NET 7中System.Text.Json的新功能:类型层次结构:
System.Text.Json现在支持用户定义类型层次结构的多态序列化和反序列化。这可以通过给类型层次结构的基类加上新的
JsonDerivedTypeAttribute
来启用。
首先,让我们考虑序列化。假设您有以下类型层次结构:
public abstract class BaseType { } // Properties omitted
public class DerivedType1 : BaseType { public string Derived1 { get; set; } }
public class DerivedType2 : BaseType { public int Derived2 { get; set; } }
如果你的数据模型中包含一个值,其声明类型为BaseType
,例如:
var list = new List<BaseType> { new DerivedType1 { Derived1 = "value 1" } };
[JsonDerivedType(typeof(DerivedType1))]
[JsonDerivedType(typeof(DerivedType2))]
public abstract class BaseType { } // Properties omitted
DerivedType1
列入白名单后,对于您的模型进行序列化:var json = JsonSerializer.Serialize(list);
结果
[{"Derived1" : "value 1"}]
演示Fiddle #1在这里。
请注意,只有通过属性白名单(或在运行时通过设置JsonTypeInfo.PolymorphismOptions
)列入白名单的派生类型可以通过此机制进行序列化。 如果您有其他未列入白名单的派生类型,例如:
public class DerivedType3 : BaseType { public string Derived3 { get; set; } }
然后JsonSerializer.Serialize(new BaseType [] { new DerivedType3 { Derived3 = "value 3" } })
会抛出一个System.NotSupportedException: Runtime type 'DerivedType3' is not supported by polymorphic type 'BaseType'
的异常。点击此处查看演示。
这就是序列化的内容。如果您需要对类型层次结构进行往返传输,则需要为每个派生类型提供一个类型鉴别器属性值。可以通过为每个派生类型提供JsonDerivedTypeAttribute.TypeDiscriminator
属性值来完成此操作:
[JsonDerivedType(typeof(DerivedType1), "DerivedType1")]
[JsonDerivedType(typeof(DerivedType2), "DerivedType2")]
public abstract class BaseType { } // Properties omitted
var json = JsonSerializer.Serialize(list);
System.Text.Json将添加一个人工类型鉴别器属性"$type"
,表示已序列化的类型:
[{"$type" : "DerivedType1", "Derived1" : "value 1"}]
完成上述操作后,您现在可以像这样反序列化您的数据模型:
var list2 = JsonSerializer.Deserialize<List<BaseType>>(json);
实际的具体类型序列化将会被保留。Demo fiddle #3 在这里。
还可以通过合同定制告知 System.Text.Json 在运行时中您的类型层级结构。当您的类型层级结构无法修改、某些派生类型位于不同的程序集且无法在编译时引用或者您正尝试在多个传统序列化程序之间进行交互时,可能需要这样做。基本的工作流程是实例化一个 DefaultJsonTypeInfoResolver
实例并添加一个修改器,该修改器为您的基础类型的JsonTypeInfo
设置必要的PolymorphismOptions
。
例如,可以在运行时启用BaseType
层次结构的多态序列化,方法如下:
var resolver = new DefaultJsonTypeInfoResolver
{
Modifiers =
{
// Add an Action<JsonTypeInfo> modifier that sets up the polymorphism options for BaseType
static typeInfo =>
{
if (typeInfo.Type != typeof(BaseType))
return;
typeInfo.PolymorphismOptions = new()
{
DerivedTypes =
{
new JsonDerivedType(typeof(DerivedType1), "Derived1"),
new JsonDerivedType(typeof(DerivedType2), "Derived2")
}
};
},
// Add other modifiers as required.
}
};
var options = new JsonSerializerOptions
{
TypeInfoResolver = resolver,
// Add other options as required
};
var json = JsonSerializer.Serialize(list, options);
这里是示例代码 #4,点此链接查看。
注:
The whitelisting approach is consistent with the approach of the data contract serializers, which use the KnownTypeAttribute
, and XmlSerializer
, which uses XmlIncludeAttribute
. It is inconsistent with Json.NET, whose TypeNameHandling
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:
[JsonDerivedType(typeof(DerivedType1), 1)]
[JsonDerivedType(typeof(DerivedType2), 2)]
public abstract class BaseType { } // Properties omitted
Then serializing the list above results in
[{"$type" : 1, "Derived1" : "value 1"}]
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 by DataContractJsonSerializer
, apply JsonPolymorphicAttribute
to the base type and set TypeDiscriminatorPropertyName
like so:
[JsonPolymorphic(TypeDiscriminatorPropertyName = "__type")]
[JsonDerivedType(typeof(DerivedType1), "DerivedType1")]
[JsonDerivedType(typeof(DerivedType2), "DerivedType2")]
public abstract class BaseType { } // Properties omitted
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:
JsonUnknownDerivedTypeHandling Value Meaning FailSerialization 0 An object of undeclared runtime type will fail polymorphic serialization. FallBackToBaseType 1 An object of undeclared runtime type will fall back to the serialization contract of the base type. FallBackToNearestAncestor 2 An object of undeclared runtime type will revert to the serialization contract of the nearest declared ancestor type. Certain interface hierarchies are not supported due to diamond ambiguity constraints.
DerivedModel
添加[JsonDerivedType(typeof(DerivedDerivedModel), nameof(DerivedDerivedModel))]
,或编写自定义的TypeInfo修改器来自动完成此操作。 - dbcobject
,它也会丢失 $type
。 - Ewanpublic class TypeDiscriminatorConverter<T> : JsonConverter<T> where T : ITypeDiscriminator
{
private readonly IEnumerable<Type> _types;
public TypeDiscriminatorConverter()
{
var type = typeof(T);
_types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => type.IsAssignableFrom(p) && p.IsClass && !p.IsAbstract)
.ToList();
}
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}
using (var jsonDocument = JsonDocument.ParseValue(ref reader))
{
if (!jsonDocument.RootElement.TryGetProperty(nameof(ITypeDiscriminator.TypeDiscriminator), out var typeProperty))
{
throw new JsonException();
}
var type = _types.FirstOrDefault(x => x.Name == typeProperty.GetString());
if (type == null)
{
throw new JsonException();
}
var jsonObject = jsonDocument.RootElement.GetRawText();
var result = (T) JsonSerializer.Deserialize(jsonObject, type, options);
return result;
}
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, (object)value, options);
}
}
public interface ITypeDiscriminator
{
string TypeDiscriminator { get; }
}
还有示例模型
public interface ISurveyStepResult : ITypeDiscriminator
{
string Id { get; set; }
}
public class BoolStepResult : ISurveyStepResult
{
public string Id { get; set; }
public string TypeDiscriminator => nameof(BoolStepResult);
public bool Value { get; set; }
}
public class TextStepResult : ISurveyStepResult
{
public string Id { get; set; }
public string TypeDiscriminator => nameof(TextStepResult);
public string Value { get; set; }
}
public class StarsStepResult : ISurveyStepResult
{
public string Id { get; set; }
public string TypeDiscriminator => nameof(StarsStepResult);
public int Value { get; set; }
}
以下是测试方法:
public void SerializeAndDeserializeTest()
{
var surveyResult = new SurveyResultModel()
{
Id = "id",
SurveyId = "surveyId",
Steps = new List<ISurveyStepResult>()
{
new BoolStepResult(){ Id = "1", Value = true},
new TextStepResult(){ Id = "2", Value = "some text"},
new StarsStepResult(){ Id = "3", Value = 5},
}
};
var jsonSerializerOptions = new JsonSerializerOptions()
{
Converters = { new TypeDiscriminatorConverter<ISurveyStepResult>()},
WriteIndented = true
};
var result = JsonSerializer.Serialize(surveyResult, jsonSerializerOptions);
var back = JsonSerializer.Deserialize<SurveyResultModel>(result, jsonSerializerOptions);
var result2 = JsonSerializer.Serialize(back, jsonSerializerOptions);
Assert.IsTrue(back.Steps.Count == 3
&& back.Steps.Any(x => x is BoolStepResult)
&& back.Steps.Any(x => x is TextStepResult)
&& back.Steps.Any(x => x is StarsStepResult)
);
Assert.AreEqual(result2, result);
}
System.Text.Json
允许的范围内工作。 - CocowallaJsonSerializer.Deserialize(this JsonDocument document, Type returnType, JsonSerializerOptions? options = null)
从JsonDocument
反序列化,如System.Text.Json.JsonElement ToObject workaround所示。这避免了对RootElement.GetRawText();
的调用。 - dbc在新的.NET 7功能中,我们可以不用编写繁琐的代码来实现这个功能。详情请参见:https://devblogs.microsoft.com/dotnet/announcing-dotnet-7-preview-5/
[JsonDerivedType(typeof(Derived1), 0)]
[JsonDerivedType(typeof(Derived2), 1)]
[JsonDerivedType(typeof(Derived3), 2)]
public class Base { }
JsonSerializer.Serialize<Base>(new Derived2()); // { "$type" : 1, ... }
希望这可以帮助到您。
public class WeatherForecast
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}
public class WeatherForecastDerived : WeatherForecast
{
public int WindSpeed { get; set; }
}
为了让框架知道鉴别器值和类型之间的映射关系,必须手动将继承类注册到鉴别器约定注册表中:
JsonSerializerOptions options = new JsonSerializerOptions();
options.SetupExtensions();
DiscriminatorConventionRegistry registry = options.GetDiscriminatorConventionRegistry();
registry.RegisterType<WeatherForecastDerived>();
string json = JsonSerializer.Serialize<WeatherForecast>(weatherForecastDerived, options);
结果:
{
"$type": "Tests.WeatherForecastDerived, Tests",
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot",
"WindSpeed": 35
}
TypeNameHandling
功能存在类似的$type
安全问题。请参见:https://github.com/dotnet/corefx/issues/41347#issuecomment-535779492 - ahsonkhan这是适用于所有抽象类型的JsonConverter:
private class AbstractClassConverter : JsonConverter<object>
{
public override object Read(ref Utf8JsonReader reader, Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null) return null;
if (reader.TokenType != JsonTokenType.StartObject)
throw new JsonException("JsonTokenType.StartObject not found.");
if (!reader.Read() || reader.TokenType != JsonTokenType.PropertyName
|| reader.GetString() != "$type")
throw new JsonException("Property $type not found.");
if (!reader.Read() || reader.TokenType != JsonTokenType.String)
throw new JsonException("Value at $type is invalid.");
string assemblyQualifiedName = reader.GetString();
var type = Type.GetType(assemblyQualifiedName);
using (var output = new MemoryStream())
{
ReadObject(ref reader, output, options);
return JsonSerializer.Deserialize(output.ToArray(), type, options);
}
}
private void ReadObject(ref Utf8JsonReader reader, Stream output, JsonSerializerOptions options)
{
using (var writer = new Utf8JsonWriter(output, new JsonWriterOptions
{
Encoder = options.Encoder,
Indented = options.WriteIndented
}))
{
writer.WriteStartObject();
var objectIntend = 0;
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonTokenType.None:
case JsonTokenType.Null:
writer.WriteNullValue();
break;
case JsonTokenType.StartObject:
writer.WriteStartObject();
objectIntend++;
break;
case JsonTokenType.EndObject:
writer.WriteEndObject();
if(objectIntend == 0)
{
writer.Flush();
return;
}
objectIntend--;
break;
case JsonTokenType.StartArray:
writer.WriteStartArray();
break;
case JsonTokenType.EndArray:
writer.WriteEndArray();
break;
case JsonTokenType.PropertyName:
writer.WritePropertyName(reader.GetString());
break;
case JsonTokenType.Comment:
writer.WriteCommentValue(reader.GetComment());
break;
case JsonTokenType.String:
writer.WriteStringValue(reader.GetString());
break;
case JsonTokenType.Number:
writer.WriteNumberValue(reader.GetInt32());
break;
case JsonTokenType.True:
case JsonTokenType.False:
writer.WriteBooleanValue(reader.GetBoolean());
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
{
writer.WriteStartObject();
var valueType = value.GetType();
var valueAssemblyName = valueType.Assembly.GetName();
writer.WriteString("$type", $"{valueType.FullName}, {valueAssemblyName.Name}");
var json = JsonSerializer.Serialize(value, value.GetType(), options);
using (var document = JsonDocument.Parse(json, new JsonDocumentOptions
{
AllowTrailingCommas = options.AllowTrailingCommas,
MaxDepth = options.MaxDepth
}))
{
foreach (var jsonProperty in document.RootElement.EnumerateObject())
jsonProperty.WriteTo(writer);
}
writer.WriteEndObject();
}
public override bool CanConvert(Type typeToConvert) =>
typeToConvert.IsAbstract && !EnumerableInterfaceType.IsAssignableFrom(typeToConvert);
}
case JsonTokenType.Number:
- 也可能是浮点数。 - Michal Dobrodenkapublic class TypeDiscriminatorConverter<T> : JsonConverter<T>
{
private readonly IEnumerable<Type> _types;
public TypeDiscriminatorConverter()
{
var type = typeof(T);
var knownTypes = type.GetCustomAttributes(typeof(KnownTypeAttribute), false).OfType<KnownTypeAttribute>();
_types = knownTypes.Select(x => x.Type).ToArray();
}
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}
using (var jsonDocument = JsonDocument.ParseValue(ref reader))
{
if (!jsonDocument.RootElement.TryGetProperty("discriminator",
out var typeProperty))
{
throw new JsonException();
}
var type = _types.FirstOrDefault(x => x.FullName == typeProperty.GetString());
if (type == null)
{
throw new JsonException();
}
var jsonObject = jsonDocument.RootElement.GetRawText();
var result = (T)JsonSerializer.Deserialize(jsonObject, type, options);
return result;
}
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
writer.WriteStartObject();
using (JsonDocument document = JsonDocument.Parse(JsonSerializer.Serialize(value)))
{
writer.WritePropertyName("discriminator");
writer.WriteStringValue(value.GetType().FullName);
foreach (var property in document.RootElement.EnumerateObject())
{
property.WriteTo(writer);
}
}
writer.WriteEndObject();
}
}
[JsonConverter(typeof(JsonInheritanceConverter))]
[KnownType(typeof(DerivedA))]
[KnownType(typeof(DerivedB))]
public abstract class BaseClass
{
//..
}
JsonConverter
是来自Newtonsoft,但它与System.Text.Json无关。 - Evgeni Nabokovpublic abstract record QueryClause(); // the abstract is kind of important
public record AndClause(QueryClause[] SubClauses) : QueryClause();
public record OrClause(QueryClause[] SubClauses) : QueryClause();
// ...
JsonSerializerOptions options = new JsonSerializerOptions();
options.Converters.Add(new BaseClassConverter<QueryClause>(
typeof(AndClause),
typeof(OrClause)));
// ...
转换器
public class BaseClassConverter<TBaseType> : JsonConverter<TBaseType>
where TBaseType : class
{
private readonly Type[] _types;
private const string TypeProperty = "$type";
public BaseClassConverter(params Type[] types)
{
_types = types;
}
public override bool CanConvert(Type type)
=> typeof(TBaseType) == type; // only responsible for the abstract base
public override TBaseType Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
TBaseType result;
if (JsonDocument.TryParseValue(ref reader, out var doc))
{
if (doc.RootElement.TryGetProperty(TypeProperty, out var typeProperty))
{
var typeName = typeProperty.GetString();
var type = _types.FirstOrDefault(t => t.Name == typeName) ?? throw new JsonException($"{TypeProperty} specifies an invalid type");
var rootElement = doc.RootElement.GetRawText();
result = JsonSerializer.Deserialize(rootElement, type, options) as TBaseType ?? throw new JsonException("target type could not be serialized");
}
else
{
throw new JsonException($"{TypeProperty} missing");
}
}
else
{
throw new JsonException("Failed to parse JsonDocument");
}
return result;
}
public override void Write(
Utf8JsonWriter writer,
TBaseType value,
JsonSerializerOptions options)
{
var type = value.GetType();
if (_types.Any(t => type.Name == t.Name))
{
var jsonElement = JsonSerializer.SerializeToElement(value, type, options);
var jsonObject = JsonObject.Create(jsonElement) ?? throw new JsonException();
jsonObject[TypeProperty] = type.Name;
jsonObject.WriteTo(writer, options);
}
else
{
throw new JsonException($"{type.Name} with matching base type {typeof(TBaseType).Name} is not registered.");
}
}
}
doc.Deserialize(type, options)
- 这样会更有效率。另外,如果你反转测试,你可以展平嵌套的if(!JsonDocument.TryParseValue...) throw ...
。 - Rafael Munitić我非常喜欢Demetrius的答案,但是我认为在可重复使用性方面还可以更进一步。我想到了以下解决方案:
JsonConverterFactory:
/// <summary>
/// Represents the <see cref="JsonConverterFactory"/> used to create <see cref="AbstractClassConverter{T}"/>
/// </summary>
public class AbstractClassConverterFactory
: JsonConverterFactory
{
/// <summary>
/// Gets a <see cref="Dictionary{TKey, TValue}"/> containing the mappings of types to their respective <see cref="JsonConverter"/>
/// </summary>
protected static Dictionary<Type, JsonConverter> Converters = new Dictionary<Type, JsonConverter>();
/// <summary>
/// Initializes a new <see cref="AbstractClassConverterFactory"/>
/// </summary>
/// <param name="namingPolicy">The current <see cref="JsonNamingPolicy"/></param>
public AbstractClassConverterFactory(JsonNamingPolicy namingPolicy)
{
this.NamingPolicy = namingPolicy;
}
/// <summary>
/// Gets the current <see cref="JsonNamingPolicy"/>
/// </summary>
protected JsonNamingPolicy NamingPolicy { get; }
/// <inheritdoc/>
public override bool CanConvert(Type typeToConvert)
{
return typeToConvert.IsClass && typeToConvert.IsAbstract && typeToConvert.IsDefined(typeof(DiscriminatorAttribute));
}
/// <inheritdoc/>
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
if(!Converters.TryGetValue(typeToConvert, out JsonConverter converter))
{
Type converterType = typeof(AbstractClassConverter<>).MakeGenericType(typeToConvert);
converter = (JsonConverter)Activator.CreateInstance(converterType, this.NamingPolicy);
Converters.Add(typeToConvert, converter);
}
return converter;
}
}
/// <summary>
/// Represents the <see cref="JsonConverter"/> used to convert to/from an abstract class
/// </summary>
/// <typeparam name="T">The type of the abstract class to convert to/from</typeparam>
public class AbstractClassConverter<T>
: JsonConverter<T>
{
/// <summary>
/// Initializes a new <see cref="AbstractClassConverter{T}"/>
/// </summary>
/// <param name="namingPolicy">The current <see cref="JsonNamingPolicy"/></param>
public AbstractClassConverter(JsonNamingPolicy namingPolicy)
{
this.NamingPolicy = namingPolicy;
DiscriminatorAttribute discriminatorAttribute = typeof(T).GetCustomAttribute<DiscriminatorAttribute>();
if (discriminatorAttribute == null)
throw new NullReferenceException($"Failed to find the required '{nameof(DiscriminatorAttribute)}'");
this.DiscriminatorProperty = typeof(T).GetProperty(discriminatorAttribute.Property, BindingFlags.Default | BindingFlags.Public | BindingFlags.Instance);
if (this.DiscriminatorProperty == null)
throw new NullReferenceException($"Failed to find the specified discriminator property '{discriminatorAttribute.Property}' in type '{typeof(T).Name}'");
this.TypeMappings = new Dictionary<string, Type>();
foreach (Type derivedType in TypeCacheUtil.FindFilteredTypes($"nposm:json-polymorph:{typeof(T).Name}",
(t) => t.IsClass && !t.IsAbstract && t.BaseType == typeof(T)))
{
DiscriminatorValueAttribute discriminatorValueAttribute = derivedType.GetCustomAttribute<DiscriminatorValueAttribute>();
if (discriminatorValueAttribute == null)
continue;
string discriminatorValue = null;
if (discriminatorValueAttribute.Value.GetType().IsEnum)
discriminatorValue = EnumHelper.Stringify(discriminatorValueAttribute.Value, this.DiscriminatorProperty.PropertyType);
else
discriminatorValue = discriminatorValueAttribute.Value.ToString();
this.TypeMappings.Add(discriminatorValue, derivedType);
}
}
/// <summary>
/// Gets the current <see cref="JsonNamingPolicy"/>
/// </summary>
protected JsonNamingPolicy NamingPolicy { get; }
/// <summary>
/// Gets the discriminator <see cref="PropertyInfo"/> of the abstract type to convert
/// </summary>
protected PropertyInfo DiscriminatorProperty { get; }
/// <summary>
/// Gets an <see cref="Dictionary{TKey, TValue}"/> containing the mappings of the converted type's derived types
/// </summary>
protected Dictionary<string, Type> TypeMappings { get; }
/// <inheritdoc/>
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
throw new JsonException("Start object token type expected");
using (JsonDocument jsonDocument = JsonDocument.ParseValue(ref reader))
{
string discriminatorPropertyName = this.NamingPolicy?.ConvertName(this.DiscriminatorProperty.Name);
if (!jsonDocument.RootElement.TryGetProperty(discriminatorPropertyName, out JsonElement discriminatorProperty))
throw new JsonException($"Failed to find the required '{this.DiscriminatorProperty.Name}' discriminator property");
string discriminatorValue = discriminatorProperty.GetString();
if (!this.TypeMappings.TryGetValue(discriminatorValue, out Type derivedType))
throw new JsonException($"Failed to find the derived type with the specified discriminator value '{discriminatorValue}'");
string json = jsonDocument.RootElement.GetRawText();
return (T)JsonSerializer.Deserialize(json, derivedType);
}
}
/// <inheritdoc/>
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, (object)value, options);
}
}
DiscriminatorAttribute:
/// <summary>
/// Represents the <see cref="Attribute"/> used to indicate the property used to discriminate derived types of the marked class
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class DiscriminatorAttribute
: Attribute
{
/// <summary>
/// Initializes a new <see cref="DiscriminatorAttribute"/>
/// </summary>
/// <param name="property">The name of the property used to discriminate derived types of the class marked by the <see cref="DiscriminatorAttribute"/></param>
public DiscriminatorAttribute(string property)
{
this.Property = property;
}
/// <summary>
/// Gets the name of the property used to discriminate derived types of the class marked by the <see cref="DiscriminatorAttribute"/>
/// </summary>
public string Property { get; }
}
DiscriminatorValueAttribute:
/// <summary>
/// Represents the <see cref="Attribute"/> used to indicate the discriminator value of a derived type
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class DiscriminatorValueAttribute
: Attribute
{
/// <summary>
/// Initializes a new <see cref="DiscriminatorValueAttribute"/>
/// </summary>
/// <param name="value">The value used to discriminate the derived type marked by the <see cref="DiscriminatorValueAttribute"/></param>
public DiscriminatorValueAttribute(object value)
{
this.Value = value;
}
/// <summary>
/// Gets the value used to discriminate the derived type marked by the <see cref="DiscriminatorValueAttribute"/>
/// </summary>
public object Value { get; }
}
最后,这是一个有关如何在类上使用它的示例:
[Discriminator(nameof(Type))]
public abstract class Identity
{
public virtual IdentityType Type { get; protected set; }
}
[DiscriminatorValue(IdentityType.Person)]
public class Person
: Identity
{
}
好的,马上开始翻译。
接下来要做的就是注册工厂:
完成了!
this.Services.AddControllersWithViews()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new AbstractClassConverterFactory(options.JsonSerializerOptions.PropertyNamingPolicy));
});
https://github.com/wivuu/Wivuu.JsonPolymorphism
生成器会查看标有鉴别器属性的属性类型,然后寻找继承自持有鉴别器类型的类型来与枚举的每个情况相匹配。enum AnimalType
{
Insect,
Mammal,
Reptile,
Bird // <- This causes an easy to understand build error if it's missing a corresponding inherited type!
}
// My base type is 'Animal'
abstract partial record Animal( [JsonDiscriminator] AnimalType type, string Name );
// Animals with type = 'Insect' will automatically deserialize as `Insect`
record Insect(int NumLegs = 6, int NumEyes=4) : Animal(AnimalType.Insect, "Insectoid");
record Mammal(int NumNipples = 2) : Animal(AnimalType.Mammal, "Mammalian");
record Reptile(bool ColdBlooded = true) : Animal(AnimalType.Reptile, "Reptilian");
abstract class A {} class B:A{} class C:A{}
。API 有参数IEnumerable<A>
。客户端发送new A[]{new B(), new C()}
。ASP 通过 Newtonsoft json 进行反序列化,它可以工作。如何在 asp.net core 3.0 中使用 system.text.json 实现这个功能? - SkyStormBusiness
是一个接口,它也不会改变。如果你搜索System.Text.Json polymorphism
,你会发现这个问题,它解释了目前不支持多态反序列化。 - Panagiotis Kanavos