gpt4 book ai didi

c# - 带接口(interface)的 JsonConverter

转载 作者:行者123 更新时间:2023-12-04 12:25:39 24 4
gpt4 key购买 nike

我有一个来自客户端并自动从 Web Api 2 反序列化的对象。

现在我的模型的一个属性有问题。此属性“CurrentField”属于 IField 类型,此接口(interface)有 2 种不同的实现。

这是我的模型(只是一个假人)

public class MyTest
{
public IField CurrentField {get;set;}
}

public interface IField{
string Name {get;set;}
}

public Field1 : IField{
public string Name {get;set;}
public int MyValue {get;set;}
}

public Field2 : IField{
public string Name {get;set;}
public string MyStringValue {get;set;}
}

我试图创建一个自定义 JsonConverter 来找出我的客户端对象是什么类型(Field1 或 Field2),但我不知道如何。

我的转换器被调用,我可以在调用时看到对象
var obj = JObject.load(reader);

但我怎样才能知道它是什么类型呢?我不能做类似的事情
if(obj is Field1) ...

这是我应该检查的方法吗?
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)

最佳答案

使用 Json.NET 反序列化接口(interface)时如何自动选择具体类型

解决您的问题的最简单方法是使用 TypeNameHandling = TypeNameHandling.Auto 序列化和反序列化您的 JSON(在客户端和服务器端)。 .如果这样做,您的 JSON 将包含为 IFIeld 序列化的实际类型。属性(property),像这样:

{
"CurrentField": {
"$type": "MyNamespace.Field2, MyAssembly",
"Name": "name",
"MyStringValue": "my string value"
}
}


但是,请注意 Newtonsoft docs 中的警告:

TypeNameHandling should be used with caution when your application deserializes JSON from an external source. Incoming types should be validated with a custom SerializationBinder when deserializing with a value other than None.



有关为什么这可能是必要的讨论,请参阅 TypeNameHandling caution in Newtonsoft Json , How to configure Json.NET to create a vulnerable web API ,以及 Alvaro Muñoz 和 Oleksandr Mirosh 的黑帽论文 https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf

如果由于某种原因您无法更改服务器输出的内容,您可以创建一个 JsonConverter将 JSON 加载到 JObject并检查实际存在哪些字段,然后搜索可能的具体类型以找到具有相同属性的类型:
public class JsonDerivedTypeConverer<T> : JsonConverter
{
public JsonDerivedTypeConverer() { }

public JsonDerivedTypeConverer(params Type[] types)
{
this.DerivedTypes = types;
}

readonly HashSet<Type> derivedTypes = new HashSet<Type>();

public IEnumerable<Type> DerivedTypes
{
get
{
return derivedTypes.ToArray();
}
set
{
if (value == null)
throw new ArgumentNullException();
derivedTypes.Clear();
if (value != null)
derivedTypes.UnionWith(value);
}
}

JsonObjectContract FindContract(JObject obj, JsonSerializer serializer)
{
List<JsonObjectContract> bestContracts = new List<JsonObjectContract>();
foreach (var type in derivedTypes)
{
if (type.IsAbstract)
continue;
var contract = serializer.ContractResolver.ResolveContract(type) as JsonObjectContract;
if (contract == null)
continue;
if (obj.Properties().Select(p => p.Name).Any(n => contract.Properties.GetClosestMatchProperty(n) == null))
continue;
if (bestContracts.Count == 0 || bestContracts[0].Properties.Count > contract.Properties.Count)
{
bestContracts.Clear();
bestContracts.Add(contract);
}
else if (contract.Properties.Count == bestContracts[0].Properties.Count)
{
bestContracts.Add(contract);
}
}
return bestContracts.Single();
}

public override bool CanConvert(Type objectType)
{
return objectType == typeof(T);
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var obj = JObject.Load(reader); // Throws an exception if the current token is not an object.
var contract = FindContract(obj, serializer);
if (contract == null)
throw new JsonSerializationException("no contract found for " + obj.ToString());
if (existingValue == null || !contract.UnderlyingType.IsAssignableFrom(existingValue.GetType()))
existingValue = contract.DefaultCreator();
using (var sr = obj.CreateReader())
{
serializer.Populate(sr, existingValue);
}
return existingValue;
}

public override bool CanWrite { get { return false; } }

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

然后你可以将它作为转换器应用到 IField :
[JsonConverter(typeof(JsonDerivedTypeConverer<IField>), new object [] { new Type [] { typeof(Field1), typeof(Field2) } })]
public interface IField
{
string Name { get; set; }
}

请注意,此解决方案有点脆弱。如果服务器省略了 MyStringValueMyValue字段(例如,因为它们具有默认值和 DefaultValueHandling = DefaultValueHandling.Ignore ),那么转换器将不知道要创建哪种类型并抛出异常。类似地,如果两个具体类型实现 IField具有相同的属性名称,仅类型不同,转换器将抛出异常。使用 TypeNameHandling.Auto避免了这些潜在的问题。

更新

以下版本检查 "$type"参数存在,如果 TypeNameHandling != TypeNameHandling.None , 回退到默认序列化。它必须做一些技巧来防止回退时的无限递归:
public class JsonDerivedTypeConverer<T> : JsonConverter
{
public JsonDerivedTypeConverer() { }

public JsonDerivedTypeConverer(params Type[] types)
{
this.DerivedTypes = types;
}

readonly HashSet<Type> derivedTypes = new HashSet<Type>();

public IEnumerable<Type> DerivedTypes
{
get
{
return derivedTypes.ToArray();
}
set
{
derivedTypes.Clear();
if (value != null)
derivedTypes.UnionWith(value);
}
}

JsonObjectContract FindContract(JObject obj, JsonSerializer serializer)
{
List<JsonObjectContract> bestContracts = new List<JsonObjectContract>();
foreach (var type in derivedTypes)
{
if (type.IsAbstract)
continue;
var contract = serializer.ContractResolver.ResolveContract(type) as JsonObjectContract;
if (contract == null)
continue;
if (obj.Properties().Select(p => p.Name).Where(n => n != "$type").Any(n => contract.Properties.GetClosestMatchProperty(n) == null))
continue;
if (bestContracts.Count == 0 || bestContracts[0].Properties.Count > contract.Properties.Count)
{
bestContracts.Clear();
bestContracts.Add(contract);
}
else if (contract.Properties.Count == bestContracts[0].Properties.Count)
{
bestContracts.Add(contract);
}
}
return bestContracts.Single();
}

public override bool CanConvert(Type objectType)
{
return objectType == typeof(T);
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var obj = JObject.Load(reader); // Throws an exception if the current token is not an object.
if (obj["$type"] != null && serializer.TypeNameHandling != TypeNameHandling.None)
{
// Prevent infinite recursion when using an explicit converter in the list.
var removed = serializer.Converters.Remove(this);
try
{
// Kludge to prevent infinite recursion when using JsonConverterAttribute on the type: deserialize to object.
return obj.ToObject(typeof(object), serializer);
}
finally
{
if (removed)
serializer.Converters.Add(this);
}
}
else
{
var contract = FindContract(obj, serializer);
if (contract == null)
throw new JsonSerializationException("no contract found for " + obj.ToString());
if (existingValue == null || !contract.UnderlyingType.IsAssignableFrom(existingValue.GetType()))
existingValue = contract.DefaultCreator();
using (var sr = obj.CreateReader())
{
serializer.Populate(sr, existingValue);
}
return existingValue;
}
}

public override bool CanWrite { get { return false; } }

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

关于c# - 带接口(interface)的 JsonConverter,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33321698/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com