在 JSON.NET 中用于反序列化的强制转换接口

我正在尝试建立一个阅读器,它将接收来自不同网站的 JSON 对象(想想信息抓取) ,并将它们转换成 C # 对象。我目前正在使用 JSON.NET 进行反序列化处理。我遇到的问题是它不知道如何处理类中的接口级属性。因此,一些自然的东西:

public IThingy Thing

将产生错误:

无法创建 IThingy 类型的实例。 Type 是接口或抽象类,无法实例化。

相对于 Thingy,IThingy 是相对重要的,因为我正在处理的代码被认为是敏感的,而单元测试是非常重要的。使用完全成熟的对象(如 Thingy)不可能模仿原子测试脚本的对象。它们一定是一个接口。

我仔细研究 JSON.NET 的文档已经有一段时间了,我在这个网站上找到的与此相关的问题都是一年前的了。有人帮忙吗?

另外,如果有必要的话,我的应用程序是用.NET 4.0编写的。

185327 次浏览

没有对象将 永远不会 和 IThingy 作为接口都是抽象的定义。

首先序列化的对象属于某种 混凝土类型,实现了 摘要接口。您需要让这个相同的 混凝土类重新激活序列化的数据。

然后生成的对象将是某种类型的 工具,即您正在寻找的 摘要接口。

文件可以看出,您可以使用

(Thingy)JsonConvert.DeserializeObject(jsonString, typeof(Thingy));

当反序列化时通知 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 和叶对象之间实现一个具体的基类,并将其反序列化。

第一个可能在运行时失败,第二个需要修改对象模型并将输出同质化到最小公分母。

不管怎样,大部分时间我都得自己处理。每个对象都有一个 反序列化(string jsonStream)方法。其中的一些片段:

JObject parsedJson = this.ParseJson(jsonStream);
object thingyObjectJson = (object)parsedJson["thing"];
this.Thing = new Thingy(Convert.ToString(thingyObjectJson));

在这种情况下,新的东西(字符串)是一个构造函数,它将调用适当具体类型的 反序列化(string jsonStream)方法。这个方案将继续向下和向下运行,直到您到达 json.NET 可以处理的基点。

this.Name = (string)parsedJson["name"];
this.CreatedTime = DateTime.Parse((string)parsedJson["created_time"]);

诸如此类。这个设置允许我给 json.NET 设置它可以处理,而不必重构库本身的很大一部分,或者使用笨重的 try/parse 模型,这些模型会因为涉及的对象的数量而使我们的整个库陷入困境。它还意味着我可以有效地处理特定对象上的任何 json 更改,并且不需要担心对象所涉及的任何内容。这绝不是理想的解决方案,但是通过我们的单元和集成测试,它运行得非常好。

(从 这个问题复制)

在我无法控制传入的 JSON (因此不能确保它包含 $type 属性)的情况下,我编写了一个自定义转换器,它允许您显式地指定具体的类型:

public class Model
{
[JsonConverter(typeof(ConcreteTypeConverter<Something>))]
public ISomething TheThing { get; set; }
}

这只是使用来自 Json.Net 的默认序列化器实现,同时显式地指定具体类型。

一个概述是可用的 在这篇博客上。源代码如下:

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);
}
}

要启用接口的多个实现的反序列化,可以使用 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 对象不需要嵌入具体的类型名称。

这个 所以邮局通过一个通用的 JsonConverter 进一步提供了相同的解决方案。

@ SamualDavis 在 相关问题中提供了一个很棒的解决方案,我将在这里总结一下。

如果您必须将 JSON 流反序列化为具有接口属性的具体类,那么可以使用 包含具体的类作为类的构造函数的参数! 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; }
}

对于那些可能对 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);
}
}

我对这个问题的解决方案是这样的:

/// <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();
}
}

}

很明显,你可以通过添加一个构造函数把它转换成一个更通用的转换器,这个构造函数采用 Dictionary < Type,Type > 类型的参数来实例化转换实例变量。

我发现这个很有用,你也可以。

示例用法

public class Parent
{
[JsonConverter(typeof(InterfaceConverter<IChildModel, ChildModel>))]
IChildModel Child { get; set; }
}

自定义创建转换器

public class InterfaceConverter<TInterface, TConcrete> : CustomCreationConverter<TInterface>
where TConcrete : TInterface, new()
{
public override TInterface Create(Type objectType)
{
return new TConcrete();
}
}

Json.NET 文档

为什么要使用转换器? Newtonsoft.Json中有一个本地功能可以解决这个问题:

JsonSerializerSettings中的 TypeNameHandling设置为 TypeNameHandling.Auto

JsonConvert.SerializeObject(
toSerialize,
new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto
});

这将把每个类型放到 json 中,json 不是作为类型的具体实例,而是作为接口或抽象类。

确保您正在使用 序列化和反序列化的相同设置

我测试过了,效果非常好,即使是在列表的时候。

搜寻结果 带有网站链接的 Web 结果

Something 警告:

只能从已知和可信的来源为 json 使用这个。用户 剪剪剪正确地提到这确实是一个漏洞。

有关更多信息,请参见 CA2328SCS0028


资料来源和替代手工执行: 博客内部代码

假设一个 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;
}
}

那么,假设你的班级是这样的:

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; }
}
}

因此,解析器在反序列化中的用法可能类似于:

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中看到更多细节

几年过去了,我也遇到过类似的问题。在我的例子中,存在大量嵌套的接口,并且偏好在运行时生成具体的类,以便 It 能够与泛型类一起工作。

我决定在运行时创建一个代理类来包装 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>();

我的解决方案是在构造函数中添加了接口元素。

public class Customer: ICustomer{
public Customer(Details details){
Details = details;
}


[JsonProperty("Details",NullValueHnadling = NullValueHandling.Ignore)]
public IDetails Details {get; set;}
}

为了将抽象类型映射到实类型,可以使用这个类:

public class AbstractConverter<TReal, TAbstract>
: JsonConverter where TReal : TAbstract
{
public override Boolean CanConvert(Type objectType)
=> objectType == typeof(TAbstract);


public override Object ReadJson(JsonReader reader, Type type, Object value, JsonSerializer jser)
=> jser.Deserialize<TReal>(reader);


public override void WriteJson(JsonWriter writer, Object value, JsonSerializer jser)
=> jser.Serialize(writer, value);
}

当反序列化时:

var settings = new JsonSerializerSettings
{
Converters = {
new AbstractConverter<Thing, IThingy>(),
new AbstractConverter<Thing2, IThingy2>()
},
};


JsonConvert.DeserializeObject(json, type, settings);

Nicholas Westby 在 很棒的文章中提供了一个很好的解决方案。

如果您希望将 JSON 反序列化为实现这样的接口的许多可能类之一:

public class Person
{
public IProfession Profession { get; set; }
}


public interface IProfession
{
string JobTitle { get; }
}


public class Programming : IProfession
{
public string JobTitle => "Software Developer";
public string FavoriteLanguage { get; set; }
}


public class Writing : IProfession
{
public string JobTitle => "Copywriter";
public string FavoriteWord { get; set; }
}


public class Samples
{
public static Person GetProgrammer()
{
return new Person()
{
Profession = new Programming()
{
FavoriteLanguage = "C#"
}
};
}
}

您可以使用自定义的 JSON 转换器:

public class ProfessionConverter : JsonConverter
{
public override bool CanWrite => false;
public override bool CanRead => true;
public override bool CanConvert(Type objectType)
{
return objectType == typeof(IProfession);
}
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 profession = default(IProfession);
switch (jsonObject["JobTitle"].Value())
{
case "Software Developer":
profession = new Programming();
break;
case "Copywriter":
profession = new Writing();
break;
}
serializer.Populate(jsonObject.CreateReader(), profession);
return profession;
}
}

您还需要使用 JsonConverter 属性来装饰“ Professional”属性,让它知道如何使用您的自定义转换器:

    public class Person
{
[JsonConverter(typeof(ProfessionConverter))]
public IProfession Profession { get; set; }
}

然后,您可以使用 Interface 强制转换您的类:

Person person = JsonConvert.DeserializeObject<Person>(jsonString);

使用这个 类型,它的使用方法非常类似,它只是添加了 json 的鉴别器:

[JsonConverter(typeof(JsonKnownTypeConverter<Interface1>))]
[JsonKnownType(typeof(MyClass), "myClass")]
public interface Interface1
{  }
public class MyClass : Interface1
{
public string Something;
}

现在,当您在 json 中序列化对象时,将添加具有 "myClass"值的 "$type",它将用于反序列化

杰森:

{"Something":"something", "$type":"derived"}

您还可以使用自定义 TextInputFormatter (不需要外部库) ,这也有助于您了解如何处理任何类型数据的(反)序列化。

public class MyInputTypeFormatter : TextInputFormatter
{
public MyInputTypeFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json"));
SupportedEncodings.Add(Encoding.UTF8);
}






protected override bool CanReadType(Type type)
{
return type == typeof(MyClass);
}


public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
{
var httpContext = context.HttpContext;
var serviceProvider = httpContext.RequestServices;
var logger = serviceProvider.GetRequiredService<ILogger<ImageTypeConverter>>();
using var reader = new StreamReader(httpContext.Request.Body, encoding);
{
var data = await reader.ReadToEndAsync();
if (data.Contains("Hello"))
{
var myClass= new MyClass(data);
return await InputFormatterResult.SuccessAsync(myClass);


}
else
{
return await InputFormatterResult.FailureAsync();


}
}




}
}

然后,简单地将此输入格式化程序添加到输入格式化程序列表

services.AddControllers(options=> {
options.InputFormatters.Insert(0, new MyInputFormatter());
});

0表示这是模型绑定时调用的第一个输入格式化程序。

看起来工作量很大,但大部分都是样板文件。 我会解释这是怎么回事,

您有一个 action 方法/路由,其参数为 MyClass 类型。当一个请求到达它时,您的输入格式化程序的 CanReadType 被调用,并且它返回 true,这意味着它将处理反序列化。然后调用 ReadRequestBodyAsync 方法,并向其提供请求数据。

您可以对数据进行任何处理,如果反序列化成功,则返回 MyClass 类型的对象。否则你只是返回一个失败。

在反序列化中可以使用

using (JsonDocument document = JsonDocument.Parse(jsonString))
{
JsonElement root = document.RootElement;
// ...
}

您可以遍历元素,因为输入被解析成一个 json 对象,然后保存到一个 DOM 中。然后,您就可以看到它们包含什么,并用它们的数据手动创建类,并将输入作为接口转换为类。

注意: JsonDocument 是在.Net 3.1中引入的 你可以在这里查看如何使用它

有关如何使用 TextInputFormatter 和 TextOutputFormatter 的详细信息 使用自定义输入格式化程序的好处是,它为处理可能使用多个接口的自定义类提供了一个中心类。它还为您提供了对处理输入数据的良好控制。