在JSON.NET中为反序列化投射接口
我试图build立一个读取器,将从各种网站的JSON对象(思考信息刮),并将其转换成C#对象。 我目前正在使用JSON.NET的反序列化过程。 我遇到的问题是它不知道如何处理类中的接口级属性。 所以有一些本质的东西:
public IThingy Thing
会产生错误:
无法创buildIThingytypes的实例。 Type是一个接口或抽象类,不能被实例化。
把它作为一个IThingy而不是Thingy是相对重要的,因为我所使用的代码被认为是敏感的,unit testing是非常重要的。 primefacestesting脚本的对象模仿是不可能的,像Thingy这样的完全对象。 他们必须是一个接口。
我一直在钻研JSON.NET的文档,现在我可以在这个网站find的相关问题都是一年多以前。 任何帮助?
另外,如果重要,我的应用程序是用.NET 4.0编写的。
@SamualDavis在一个相关的问题中提供了一个很好的解决scheme,我将在这里总结。
如果您必须将JSONstream反序列化为具有接口属性的具体类,则可以将具体类作为参数包含到该类的构造函数中! NewtonSoft解串器非常聪明,它需要使用那些具体的类来反序列化属性。
这里是一个例子:
public class Visit : IVisit { /// <summary> /// This constructor is required for the JSON deserializer to be able /// to identify concrete classes to use when deserializing the interface properties. /// </summary> public Visit(MyLocation location, Guest guest) { Location = location; Guest = guest; } public long VisitId { get; set; } public ILocation Location { get; set; } public DateTime VisitDate { get; set; } public IGuest Guest { get; set; } }
要启用接口的多个实现的反序列化,可以使用JsonConverter,但不能通过一个属性:
Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer(); serializer.Converters.Add(new DTOJsonConverter()); Interfaces.IEntity entity = serializer.Deserialize(jsonReader);
DTOJsonConverter将每个接口映射到一个具体的实现:
class DTOJsonConverter : Newtonsoft.Json.JsonConverter { private static readonly string ISCALAR_FULLNAME = typeof(Interfaces.IScalar).FullName; private static readonly string IENTITY_FULLNAME = typeof(Interfaces.IEntity).FullName; public override bool CanConvert(Type objectType) { if (objectType.FullName == ISCALAR_FULLNAME || objectType.FullName == IENTITY_FULLNAME) { return true; } return false; } public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) { if (objectType.FullName == ISCALAR_FULLNAME) return serializer.Deserialize(reader, typeof(DTO.ClientScalar)); else if (objectType.FullName == IENTITY_FULLNAME) return serializer.Deserialize(reader, typeof(DTO.ClientEntity)); throw new NotSupportedException(string.Format("Type {0} unexpected.", objectType)); } public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) { serializer.Serialize(writer, value); } }
DTOJsonConverter仅用于解串器。 序列化过程不变。 Json对象不需要embedded具体的types名称。
这个SOpost通过一个通用的JsonConverter进一步提供了相同的解决scheme。
(从这个问题复制)
在我没有控制传入的JSON(因此不能确保它包含一个$ type属性)的情况下,我写了一个自定义转换器,它允许您明确指定具体的types:
public class Model { [JsonConverter(typeof(ConcreteTypeConverter<Something>))] public ISomething TheThing { get; set; } }
这只是使用Json.Net中默认的序列化器实现,同时显式指定具体的types。
此博客文章提供了一个概述。 源代码如下:
public class ConcreteTypeConverter<TConcrete> : JsonConverter { public override bool CanConvert(Type objectType) { //assume we can convert to anything for now return true; } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { //explicitly specify the concrete type we want to create return serializer.Deserialize<TConcrete>(reader); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { //use the default serialization - it works fine serializer.Serialize(writer, value); } }
为什么使用转换器? 在Newtonsoft.Json
有一个本地的function来解决这个确切的问题:
将JsonSerializerSettings
中的TypeNameHandling.Auto
设置为TypeNameHandling.Auto
JsonConvert.SerializeObject( toSerialize, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.Auto });
这将把每个types放入json中,这不是作为一个types的具体实例,而是作为一个接口或一个抽象类。
我对它进行了testing,即使在列表中,它的作用也像魅力一样。
来源和一个替代的手动实现: 代码里面的博客
我发现这很有用。 你也可以。
用法示例
public class Parent { [JsonConverter(typeof(InterfaceConverter<IChildModel, ChildModel>))] IChildModel Child { get; set; } }
自定义创build转换器
public class InterfaceConverter<TInterface, TConcrete> : CustomCreationConverter<TInterface> where TConcrete : TInterface, new() { public override TInterface Create(Type objectType) { return new TConcrete(); } }
Json.NET文档
你可以尝试两件事情:
实现一个try / parse模型:
public class Organisation { public string Name { get; set; } [JsonConverter(typeof(RichDudeConverter))] public IPerson Owner { get; set; } } public interface IPerson { string Name { get; set; } } public class Tycoon : IPerson { public string Name { get; set; } } public class Magnate : IPerson { public string Name { get; set; } public string IndustryName { get; set; } } public class Heir: IPerson { public string Name { get; set; } public IPerson Benefactor { get; set; } } public class RichDudeConverter : JsonConverter { public override bool CanConvert(Type objectType) { return (objectType == typeof(IPerson)); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { // pseudo-code object richDude = serializer.Deserialize<Heir>(reader); if (richDude == null) { richDude = serializer.Deserialize<Magnate>(reader); } if (richDude == null) { richDude = serializer.Deserialize<Tycoon>(reader); } return richDude; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { // Left as an exercise to the reader :) throw new NotImplementedException(); } }
或者,如果你可以在你的对象模型中这样做,在IPerson和你的叶子对象之间实现一个具体的基类,并反序列化到它。
第一个可能会在运行时失败,第二个需要更改您的对象模型并将输出均匀化为最小公分母。
对于那些可能对Oliver引用的ConcreteListTypeConverter好奇的人来说,这是我的尝试:
public class ConcreteListTypeConverter<TInterface, TImplementation> : JsonConverter where TImplementation : TInterface { public override bool CanConvert(Type objectType) { return true; } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var res = serializer.Deserialize<List<TImplementation>>(reader); return res.ConvertAll(x => (TInterface) x); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { serializer.Serialize(writer, value); } }
对于它的价值,我最终不得不自己处理这个问题。 每个对象都有一个Deserialize(stringjsonStream)方法。 几个片段:
JObject parsedJson = this.ParseJson(jsonStream); object thingyObjectJson = (object)parsedJson["thing"]; this.Thing = new Thingy(Convert.ToString(thingyObjectJson));
在这种情况下, 新的Thingy(string)是一个构造函数,将调用适当具体types的Deserialize(stringjsonStream)方法。 这个scheme将继续向下和向下,直到你到了json.NET可以处理的基点。
this.Name = (string)parsedJson["name"]; this.CreatedTime = DateTime.Parse((string)parsedJson["created_time"]);
等等等等。 这个设置允许我给它可以处理的json.NET设置,而不必重构库本身的大部分,或者使用笨拙的尝试/parsing模型,由于涉及的对象的数量,我们整个库会陷入困境。 这也意味着我可以有效地处理特定对象上的任何json更改,而且我不必担心对象触及的所有内容。 这绝不是理想的解决scheme,但是它在我们的单元和集成testing中工作得非常好。
我对这一点的解决方法,我喜欢,因为它很好,一般,如下:
/// <summary> /// Automagically convert known interfaces to (specific) concrete classes on deserialisation /// </summary> public class WithMocksJsonConverter : JsonConverter { /// <summary> /// The interfaces I know how to instantiate mapped to the classes with which I shall instantiate them, as a Dictionary. /// </summary> private readonly Dictionary<Type,Type> conversions = new Dictionary<Type,Type>() { { typeof(IOne), typeof(MockOne) }, { typeof(ITwo), typeof(MockTwo) }, { typeof(IThree), typeof(MockThree) }, { typeof(IFour), typeof(MockFour) } }; /// <summary> /// Can I convert an object of this type? /// </summary> /// <param name="objectType">The type under consideration</param> /// <returns>True if I can convert the type under consideration, else false.</returns> public override bool CanConvert(Type objectType) { return conversions.Keys.Contains(objectType); } /// <summary> /// Attempt to read an object of the specified type from this reader. /// </summary> /// <param name="reader">The reader from which I read.</param> /// <param name="objectType">The type of object I'm trying to read, anticipated to be one I can convert.</param> /// <param name="existingValue">The existing value of the object being read.</param> /// <param name="serializer">The serializer invoking this request.</param> /// <returns>An object of the type into which I convert the specified objectType.</returns> public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { try { return serializer.Deserialize(reader, this.conversions[objectType]); } catch (Exception) { throw new NotSupportedException(string.Format("Type {0} unexpected.", objectType)); } } /// <summary> /// Not yet implemented. /// </summary> /// <param name="writer">The writer to which I would write.</param> /// <param name="value">The value I am attempting to write.</param> /// <param name="serializer">the serializer invoking this request.</param> public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } }
}
通过添加一个types为Dictionary <Type,Type>的参数来实例化转换实例variables的构造函数,您可以明显地将它转换成更通用的转换器。
(从我以前的答复复制)
假设一个autofac设置如下:
public class AutofacContractResolver : DefaultContractResolver { private readonly IContainer _container; public AutofacContractResolver(IContainer container) { _container = container; } protected override JsonObjectContract CreateObjectContract(Type objectType) { JsonObjectContract contract = base.CreateObjectContract(objectType); // use Autofac to create types that have been registered with it if (_container.IsRegistered(objectType)) { contract.DefaultCreator = () => _container.Resolve(objectType); } return contract; } }
那么,假设你的class级是这样的:
public class TaskController { private readonly ITaskRepository _repository; private readonly ILogger _logger; public TaskController(ITaskRepository repository, ILogger logger) { _repository = repository; _logger = logger; } public ITaskRepository Repository { get { return _repository; } } public ILogger Logger { get { return _logger; } } }
因此,parsing器在反序列化中的用法可能如下:
ContainerBuilder builder = new ContainerBuilder(); builder.RegisterType<TaskRepository>().As<ITaskRepository>(); builder.RegisterType<TaskController>(); builder.Register(c => new LogService(new DateTime(2000, 12, 12))).As<ILogger>(); IContainer container = builder.Build(); AutofacContractResolver contractResolver = new AutofacContractResolver(container); string json = @"{ 'Logger': { 'Level':'Debug' } }"; // ITaskRespository and ILogger constructor parameters are injected by Autofac TaskController controller = JsonConvert.DeserializeObject<TaskController>(json, new JsonSerializerSettings { ContractResolver = contractResolver }); Console.WriteLine(controller.Repository.GetType().Name);
您可以在http://www.newtonsoft.com/json/help/html/DeserializeWithDependencyInjection.htm中看到更多详细信息;
几年过去了,我也遇到过类似的问题。 在我的情况下,有很多嵌套的接口和在运行时生成具体类的首选项,以便它将与generics类一起工作。
我决定在运行时创build一个代理类来包装Newtonsoft返回的对象。
这种方法的优点是它不需要类的具体实现,并且可以自动处理任何深度的嵌套接口。 你可以在我的博客上看到更多。
using Castle.DynamicProxy; using Newtonsoft.Json.Linq; using System; using System.Reflection; namespace LL.Utilities.Std.Json { public static class JObjectExtension { private static ProxyGenerator _generator = new ProxyGenerator(); public static dynamic toProxy(this JObject targetObject, Type interfaceType) { return _generator.CreateInterfaceProxyWithoutTarget(interfaceType, new JObjectInterceptor(targetObject)); } public static InterfaceType toProxy<InterfaceType>(this JObject targetObject) { return toProxy(targetObject, typeof(InterfaceType)); } } [Serializable] public class JObjectInterceptor : IInterceptor { private JObject _target; public JObjectInterceptor(JObject target) { _target = target; } public void Intercept(IInvocation invocation) { var methodName = invocation.Method.Name; if(invocation.Method.IsSpecialName && methodName.StartsWith("get_")) { var returnType = invocation.Method.ReturnType; methodName = methodName.Substring(4); if (_target == null || _target[methodName] == null) { if (returnType.GetTypeInfo().IsPrimitive || returnType.Equals(typeof(string))) { invocation.ReturnValue = null; return; } } if (returnType.GetTypeInfo().IsPrimitive || returnType.Equals(typeof(string))) { invocation.ReturnValue = _target[methodName].ToObject(returnType); } else { invocation.ReturnValue = ((JObject)_target[methodName]).toProxy(returnType); } } else { throw new NotImplementedException("Only get accessors are implemented in proxy"); } } } }
用法:
var jObj = JObject.Parse(input); InterfaceType proxyObject = jObj.toProxy<InterfaceType>();
没有任何对象会成为一个IThingy,因为接口都是按照定义抽象的。
您拥有的第一个序列化的对象是一些具体的types,实现了抽象接口。 你需要有相同的具体类来恢复序列化的数据。
所得到的对象将是某种types的实现您正在寻找的抽象接口。
从文档中可以看出,你可以使用
(Thingy)JsonConvert.DeserializeObject(jsonString, typeof(Thingy));
当反序列化通知JSON.NET的具体types。
我的解决scheme是在构造函数中添加接口元素。
public class Customer: ICustomer{ public Customer(Details details){ Details = details; } [JsonProperty("Details",NullValueHnadling = NullValueHandling.Ignore)] public IDetails Details {get; set;} }