反序列化接口实例集合
本文关键字:集合 实例 接口 反序列化 | 更新日期: 2023-09-27 18:07:58
我想通过json.net序列化这段代码:
public interface ITestInterface
{
string Guid {get;set;}
}
public class TestClassThatImplementsTestInterface1
{
public string Guid { get;set; }
}
public class TestClassThatImplementsTestInterface2
{
public string Guid { get;set; }
}
public class ClassToSerializeViaJson
{
public ClassToSerializeViaJson()
{
this.CollectionToSerialize = new List<ITestInterface>();
this.CollectionToSerialize.add( new TestClassThatImplementsTestInterface2() );
this.CollectionToSerialize.add( new TestClassThatImplementsTestInterface2() );
}
List<ITestInterface> CollectionToSerialize { get;set; }
}
我想序列化/反序列化ClassToSerializeViaJson与json.net。序列化正在工作,但是反序列化给了我这个错误:
Newtonsoft.Json。JsonSerializationException:无法创建ITestInterface类型的实例。类型是接口或抽象类,不能实例化。
那么我如何反序列化List<ITestInterface>
集合呢?
我在尝试自己做这件事时发现了这个问题。在我执行了Piotr Stapp (Garath)的答案后,我被它看起来如此简单所震惊。如果我只是实现了一个已经被传递了我想要实例化的确切类型(作为字符串)的方法,为什么库没有自动绑定它?
我实际上发现我不需要任何自定义绑定器Json。Net能够做我所需要的,只要我告诉它我正在做什么。
序列化时:
string serializedJson = JsonConvert.SerializeObject(objectToSerialize, Formatting.Indented, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects,
TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple
});
反序列化时:
var deserializedObject = JsonConvert.DeserializeObject<ClassToSerializeViaJson>(serializedJson, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects
});
相关文档: Json序列化设置。. NET和TypeNameHandling设置
下面是一个完整的工作示例:
public interface ITestInterface
{
string Guid { get; set; }
}
public class TestClassThatImplementsTestInterface1 : ITestInterface
{
public string Guid { get; set; }
public string Something1 { get; set; }
}
public class TestClassThatImplementsTestInterface2 : ITestInterface
{
public string Guid { get; set; }
public string Something2 { get; set; }
}
public class ClassToSerializeViaJson
{
public ClassToSerializeViaJson()
{
this.CollectionToSerialize = new List<ITestInterface>();
}
public List<ITestInterface> CollectionToSerialize { get; set; }
}
public class TypeNameSerializationBinder : SerializationBinder
{
public string TypeFormat { get; private set; }
public TypeNameSerializationBinder(string typeFormat)
{
TypeFormat = typeFormat;
}
public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
assemblyName = null;
typeName = serializedType.Name;
}
public override Type BindToType(string assemblyName, string typeName)
{
var resolvedTypeName = string.Format(TypeFormat, typeName);
return Type.GetType(resolvedTypeName, true);
}
}
class Program
{
static void Main()
{
var binder = new TypeNameSerializationBinder("ConsoleApplication.{0}, ConsoleApplication");
var toserialize = new ClassToSerializeViaJson();
toserialize.CollectionToSerialize.Add(
new TestClassThatImplementsTestInterface1()
{
Guid = Guid.NewGuid().ToString(), Something1 = "Some1"
});
toserialize.CollectionToSerialize.Add(
new TestClassThatImplementsTestInterface2()
{
Guid = Guid.NewGuid().ToString(), Something2 = "Some2"
});
string json = JsonConvert.SerializeObject(toserialize, Formatting.Indented,
new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
Binder = binder
});
var obj = JsonConvert.DeserializeObject<ClassToSerializeViaJson>(json,
new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
Binder = binder
});
Console.ReadLine();
}
}
我也对Garath的简单性感到惊讶,并且也得出了Json库可以自动完成的结论。但我也认为它甚至比Ben Jenkinson的答案更简单(尽管我可以看到它已经被json库的开发人员自己修改了)。从我的测试来看,您所需要做的就是将TypeNameHandling设置为Auto,如下所示:
var objectToSerialize = new List<IFoo>();
// TODO: Add objects to list
var jsonString = JsonConvert.SerializeObject(objectToSerialize,
new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });
var deserializedObject = JsonConvert.DeserializeObject<List<IFoo>>(jsonString,
new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });
选自TypeNameHandling枚举文档
Auto:当对象的类型为。net类型时,包含。net类型名序列化与其声明的类型不同。请注意默认情况下不包含根序列化对象。
这是一个老问题,但我认为我应该添加一个更深入的答案(以我写的一篇文章的形式):http://skrift.io/articles/archive/bulletproof-interface-deserialization-in-jsonnet/
TLDR:而不是配置Json。. NET将类型名嵌入到序列化的JSON中,您可以使用JSON转换器来确定要使用任何自定义逻辑反序列化到哪个类。
这样做的好处是,您可以重构类型,而不必担心反序列化中断。
如果使用默认设置,则不能。JSON。. NET无法知道如何反序列化数组。但是,您可以指定要为您的接口类型使用哪种类型转换器。要了解如何做到这一点,请参阅此页面:http://blog.greatrexpectations.com/2012/08/30/deserializing-interface-properties-using-json-net/
你也可以在这个SO问题中找到关于这个问题的信息:在JSON中强制转换反序列化接口。净
可以用JSON完成。. NET和JsonSubTypes属性:
[JsonConverter(typeof(JsonSubtypes))]
[JsonSubtypes.KnownSubTypeWithProperty(typeof(Test1), "Something1")]
[JsonSubtypes.KnownSubTypeWithProperty(typeof(Test2), "Something2")]
public interface ITestInterface
{
string Guid { get; set; }
}
public class Test1 : ITestInterface
{
public string Guid { get; set; }
public string Something1 { get; set; }
}
public class Test2 : ITestInterface
{
public string Guid { get; set; }
public string Something2 { get; set; }
}
和简单:
var fromCode = new List<ITestInterface>();
// TODO: Add objects to list
var json = JsonConvert.SerializeObject(fromCode);
var fromJson = JsonConvert.DeserializeObject<List<ITestInterface>>(json);
我想要反序列化未被我的应用程序序列化的JSON,因此我需要手动指定具体实现。我对尼古拉斯的回答作了进一步的阐述。
假设我们有
public class Person
{
public ILocation Location { get;set; }
}
和
的具体实例public class Location: ILocation
{
public string Address1 { get; set; }
// etc
}
Add in this class
public class ConfigConverter<I, T> : JsonConverter
{
public override bool CanWrite => false;
public override bool CanRead => true;
public override bool CanConvert(Type objectType)
{
return objectType == typeof(I);
}
public override void WriteJson(JsonWriter writer,
object value, JsonSerializer serializer)
{
throw new InvalidOperationException("Use default serialization.");
}
public override object ReadJson(JsonReader reader,
Type objectType, object existingValue,
JsonSerializer serializer)
{
var jsonObject = JObject.Load(reader);
var deserialized = (T)Activator.CreateInstance(typeof(T));
serializer.Populate(jsonObject.CreateReader(), deserialized);
return deserialized;
}
}
然后用JsonConverter属性
定义接口public class Person
{
[JsonConverter(typeof(ConfigConverter<ILocation, Location>))]
public ILocation Location { get;set; }
}
与Inrego的回答几乎相同,但值得进一步解释:
如果你使用TypeNameHandling.Auto
,那么它只在需要时包含类型/程序集名称(即接口和基类/派生类)。所以你的JSON更简洁,更小,更具体。
这不是它相对于XML/SOAP的主要卖点之一吗?
避免TypeNameHandling。
需要为集合类型编写自己的反序列化器。
与其重复其他人已经发布的模板转换器代码(特别是Nicholas Westby,他的博客文章非常有用,上面有链接),我已经包含了反序列化接口集合的相关更改(我有一个enum接口属性来区分实现者):
public override object ReadJson(JsonReader reader,
Type objectType, object existingValue,
JsonSerializer serializer)
{
Collection<T> result = new Collection<T>();
var array = JArray.Load(reader);
foreach (JObject jsonObject in array)
{
var rule = default(T);
var value = jsonObject.Value<string>("MyDistinguisher");
MyEnum distinguisher;
Enum.TryParse(value, out distinguisher);
switch (distinguisher)
{
case MyEnum.Value1:
rule = serializer.Deserialize<Type1>(jsonObject.CreateReader());
break;
case MyEnum.Value2:
rule = serializer.Deserialize<Type2>(jsonObject.CreateReader());
break;
default:
rule = serializer.Deserialize<Type3>(jsonObject.CreateReader());
break;
}
result.Add(rule);
}
return result;
}
我希望这对下一个寻找接口集合反序列化器的人有帮助。