我有一个来自客户端的对象,并自动从Web 2反序列化。
现在,我对我的模型的一个属性有了问题。这个属性"CurrentField“是IField类型的,这个接口有两个不同的实现。
这是我的模型(只是个假人)
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(读取器)时,我可以看到对象;
但是我怎么知道它是哪种类型的呢?我不能做像
if(obj is Field1) ...这就是我应该检查的方法,对吗?
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)发布于 2015-10-24 19:57:44
如何在使用Json.NET反序列化接口时自动选择具体类型
解决问题的最简单方法是使用TypeNameHandling = TypeNameHandling.Auto序列化和反序列化JSON (客户端和服务器端)。如果这样做,您的JSON将包括IFIeld属性的实际序列化类型,如下所示:
{ "CurrentField":{ "$type":"MyNamespace.Field2,MyAssembly","Name":"name","MyStringValue":“MyStringValue”}}
但是,请注意Newtonsoft文档发出的警告
当应用程序从外部源反序列化JSON时,应该谨慎地使用TypeNameHandling。当使用除None以外的值反序列化时,应使用自定义SerializationBinder验证传入类型。
关于为什么需要这样做的讨论,请参阅Newtonsoft Json中的TypeNameHandling警告、如何配置Json.NET以创建易受攻击的web和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; }
}请注意,这个解决方案有点脆弱。如果服务器省略了MyStringValue或MyValue字段(例如,因为它们具有默认值和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();
}
}https://stackoverflow.com/questions/33321698
复制相似问题