C# 在 Asp.Net Web API 中将 Json 反序列化为派生类型
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12638741/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Deserialising Json to derived types in Asp.Net Web API
提问by Jacob
I'm calling a method of my WebAPI sending a json that I would like to match (or bind) with a model.
我正在调用我的 WebAPI 的一种方法,发送一个我想与模型匹配(或绑定)的 json。
In the controller I have a method like:
在控制器中,我有一个方法,如:
public Result Post([ModelBinder(typeof(CustomModelBinder))]MyClass model);
'MyClass', wich is given as a parameter is an abstract class. I would like that at, depending of the type of json passed, the correct inherited class is instantiated.
'MyClass',作为参数给出是一个抽象类。我希望根据传递的 json 类型,实例化正确的继承类。
To achieve it, I'm trying to implement a custom binder. The problem is that (I don't know if it's very basic but I can't find anything) I don't know how to retrieve the raw Json (or better, some kind of serialization) that comes in the request.
为了实现它,我正在尝试实现一个自定义绑定器。问题是(我不知道它是否非常基本,但我找不到任何东西)我不知道如何检索请求中的原始 Json(或者更好的某种序列化)。
I see:
我懂了:
- actionContext.Request.Content
- actionContext.Request.Content
But all methods are exposed as async. I don't know who this fits with passing the generate model to the controller method...
但是所有方法都以异步方式公开。我不知道这适合将生成模型传递给控制器方法的人...
Thanks a lot!
非常感谢!
采纳答案by Andras Zoltan
You don't need a custom model binder. Nor do you need to muck about with the request pipeline.
您不需要自定义模型绑定器。您也不需要处理请求管道。
Take a look at this other SO: How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?.
看看其他 SO:How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?.
I used this as the basis for my own solution to the same problem.
我以此作为我自己解决同一问题的基础。
Starting off with the JsonCreationConverter<T>referenced in that SO (slightly modified to fix issues with serialization of types in responses):
从JsonCreationConverter<T>该 SO 中的引用开始(稍微修改以解决响应中类型序列化的问题):
public abstract class JsonCreationConverter<T> : JsonConverter
{
/// <summary>
/// this is very important, otherwise serialization breaks!
/// </summary>
public override bool CanWrite
{
get
{
return false;
}
}
/// <summary>
/// Create an instance of objectType, based properties in the JSON object
/// </summary>
/// <param name="objectType">type of object expected</param>
/// <param name="jObject">contents of JSON object that will be
/// deserialized</param>
/// <returns></returns>
protected abstract T Create(Type objectType, JObject jObject);
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
// Load JObject from stream
JObject jObject = JObject.Load(reader);
// Create target object based on JObject
T target = Create(objectType, jObject);
// Populate the object properties
serializer.Populate(jObject.CreateReader(), target);
return target;
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And now you can annotate your type with the JsonConverterAttribute, pointing Json.Net to a custom converter:
现在您可以使用 注释您的类型JsonConverterAttribute,将 Json.Net 指向自定义转换器:
[JsonConverter(typeof(MyCustomConverter))]
public abstract class BaseClass{
private class MyCustomConverter : JsonCreationConverter<BaseClass>
{
protected override BaseClass Create(Type objectType,
Newtonsoft.Json.Linq.JObject jObject)
{
//TODO: read the raw JSON object through jObject to identify the type
//e.g. here I'm reading a 'typename' property:
if("DerivedType".Equals(jObject.Value<string>("typename")))
{
return new DerivedClass();
}
return new DefaultClass();
//now the base class' code will populate the returned object.
}
}
}
public class DerivedClass : BaseClass {
public string DerivedProperty { get; set; }
}
public class DefaultClass : BaseClass {
public string DefaultProperty { get; set; }
}
Now you can use the base type as a parameter:
现在您可以使用基本类型作为参数:
public Result Post(BaseClass arg) {
}
And if we were to post:
如果我们要发布:
{ typename: 'DerivedType', DerivedProperty: 'hello' }
Then argwould be an instance of the DerivedClass, but if we posted:
然后arg将是 的一个实例DerivedClass,但如果我们发布:
{ DefaultProperty: 'world' }
Then you'd get an instance of the DefaultClass.
然后你会得到一个DefaultClass.
EDIT - Why I prefer this method to TypeNameHandling.Auto/All
编辑 - 为什么我更喜欢这种方法 TypeNameHandling.Auto/All
I do believe that using the TypeNameHandling.Auto/Allespoused by JotaBe is not always the ideal solution. It might well be in this case - but personally I won't do it unless:
我确实相信使用TypeNameHandling.Auto/AllJotaBe 所支持的并不总是理想的解决方案。在这种情况下很可能 - 但我个人不会这样做,除非:
- My API is only evergoing to be used by me or my team
- I don't care about having a dual XML-compatible endpoint
- 我的 API只会被我或我的团队使用
- 我不在乎有一个双 XML 兼容的端点
When Json.Net TypeNameHandling.Autoor Allare used, your web server will start sending out type names in the format MyNamespace.MyType, MyAssemblyName.
当使用 Json.NetTypeNameHandling.Auto或 时All,您的 Web 服务器将开始以MyNamespace.MyType, MyAssemblyName.
I have said in comments that I think this is a security concern. Mention was made of this in some documentation I read from Microsoft. It's not mentioned any more, it seems, however I still feel it's a valid concern. I don't everwant to expose namespace-qualified type names and assembly names to the outside world. It's increasing my attack surface. So, yes, I can not have Objectproperties/parameters my API types, but who's to say the rest of my site is completely hole-free? Who's to say a future endpoint doesn't expose the ability to exploit type names? Why take that chance just because it's easier?
我在评论中说过,我认为这是一个安全问题。在我从 Microsoft 阅读的一些文档中提到了这一点。似乎不再提及它,但是我仍然觉得这是一个有效的问题。我不以往任何时候都希望名称空间限定的类型名称和程序集名称暴露给外界。它增加了我的攻击面。所以,是的,Object我的 API 类型不能有属性/参数,但谁能说我网站的其余部分完全没有漏洞?谁能说未来的端点不会公开利用类型名称的能力?为什么要抓住这个机会只是因为它更容易?
Also - if you are writing a 'proper' API, i.e. specifically for consumption by third-parties and not just for yourself, and you're using Web API, then you're most likely looking to leverage the JSON/XML content-type handling (as a minimum). See how far you get trying to write documentation that's easy to consume, which refers to all your API types differently for XML and JSON formats.
此外 - 如果您正在编写一个“适当的”API,即专门供第三方使用而不只是为您自己使用,并且您正在使用 Web API,那么您很可能希望利用 JSON/XML 内容类型处理(至少)。看看您在尝试编写易于使用的文档方面取得了多大进展,这些文档针对 XML 和 JSON 格式以不同的方式指代您的所有 API 类型。
By overriding how JSON.Net understands the type names, you can bring the two into line, making the choice between XML/JSON for your caller purely based on taste, rather than because the type names are easier to remember in one or the other.
通过覆盖 JSON.Net 理解类型名称的方式,您可以将两者结合起来,为您的调用者在 XML/JSON 之间进行选择,完全基于品味,而不是因为类型名称在一个或另一个中更容易记住。
回答by tpeczek
You can call async methods normally, your execution will be simply suspended until the method returns and you can return the model in standard manner. Just make a call like this:
您可以正常调用异步方法,您的执行将被简单地暂停,直到方法返回,您可以以标准方式返回模型。只需拨打这样的电话:
string jsonContent = await actionContext.Request.Content.ReadAsStringAsync();
It will give you raw JSON.
它会给你原始的 JSON。
回答by JotaBe
You don't need to implement it by yourself. JSON.NET has native support for it.
您不需要自己实现它。JSON.NET 对它有本机支持。
You have to specify the desired TypeNameHandling optionfor the JSON formatter, like this (in global.asaxapplication start event):
您必须为 JSON 格式化程序指定所需的 TypeNameHandling 选项,如下所示(在global.asax应用程序启动事件中):
JsonSerializerSettings serializerSettings = GlobalConfiguration.Configuration
.Formatters.JsonFormatter.SerializerSettings;
serializerSettings.TypeNameHandling = TypeNameHandling.Auto;
If you specify Auto, like in the above sample, the parameter will be deserialized to the type specified in the $typeproperty of the object. If the $typeproperty is missing, it will be deserialized to the parameter's type. So you only have to specify the type when you're passing a parameter of a derived type. (This is the most flexible option).
如果您指定Auto,就像在上面的示例中一样,参数将被反序列化为$type对象的属性中指定的类型。如果$type缺少该属性,它将被反序列化为参数的类型。因此,您只需在传递派生类型的参数时指定类型。(这是最灵活的选择)。
For example, if you pass this parameter to a Web API action:
例如,如果您将此参数传递给 Web API 操作:
var param = {
$type: 'MyNamespace.MyType, MyAssemblyName', // .NET fully qualified name
... // object properties
};
The parameter will be deserialized to an object of MyNamespace.MyTypeclass.
该参数将被反序列化为MyNamespace.MyType类对象。
This also works fro sub-properties, i.e., you can have an object like this, which specifies that an inner property is of a given type
这也适用于子属性,即你可以有一个这样的对象,它指定一个内部属性是给定的类型
var param = {
myTypedProperty: {
$type: `...`
...
};
Here you can see a sample on JSON.NET documentation of TypeNameHandling.Auto.
在这里,您可以看到有关 TypeNameHandling.Auto 的 JSON.NET 文档的示例。
This works at least since JSON.NET 4 release.
NOTE
笔记
You don't need to decorate anything with attirbutes, or do any other customization. It will work without any changes in your Web API code.
您不需要用属性装饰任何东西,也不需要进行任何其他自定义。无需更改您的 Web API 代码即可运行。
IMPORTANT NOTE
重要的提示
The $type must be the first property of the JSON serialized object. If not, it will be ignored.
$type 必须是 JSON 序列化对象的第一个属性。如果没有,它将被忽略。
COMPARISON TO CUSTOM JsonConverter/JsonConverterAttribute
与自定义 JsonConverter/JsonConverterAttribute 的比较
I'm comparing the native solution to this answer.
我正在将本机解决方案与此答案进行比较。
To implement the JsonConverter/JsonConverterAttribute:
要实现JsonConverter/ JsonConverterAttribute:
- you need to implement a custom
JsonConverter, and a customJsonConverterAttribute - you need to use attributes to mark the parameters
- you need to know beforehand the possible types expected for the parameter
- you need to implement, or change the implementation of your
JsonConverterwhenever your types or properties change - there is a code smell of magic strings, to indicate the expected property names
- you are not implementing something generic that can be used with any type
- you're reinventing the wheel
- 你需要实现一个自定义
JsonConverter,和一个自定义JsonConverterAttribute - 您需要使用属性来标记参数
- 您需要事先知道参数预期的可能类型
JsonConverter每当您的类型或属性发生变化时,您都需要实现或更改您的实现- 有一种魔法字符串的代码味道,以指示预期的属性名称
- 您没有实现可以与任何类型一起使用的通用内容
- 你在重新发明轮子
In the author of the answer there's a comment regarding security. Unless you do something wrong (like accepting a too generic type for your parameter, like Object) there is no risk of getting an instance of the wrong type: JSON.NET native solution only instantiates an object of the parameter's type, or a type derived from it (if not, you get null).
在答案的作者中有关于安全性的评论。除非你做错事(如接受过通用类型的参数,如Object)没有得到错误类型的实例的风险:JSON.NET原生解决方案只实例参数的类型的对象,或者派生的类型它(如果没有,你会得到null)。
And these are the advantages of JSON.NET native solution:
这些是 JSON.NET 原生解决方案的优势:
- you don't need to implement anything (you only have to configure the
TypeNameHandlingonce in your app) - you don't need to use attributes in your action parameters
- you don't need to know the possible parameter types beforehand: you simply need to know the base type, and specify it in the parameter (it could be an abstract type, to make polymorphism more obvious)
- the solution works for most cases (1)without changing anything
- this solution is widely tested, and optimized
- you don't need magic strings
- the implementation is generic, and will accept any derived type
- 你不需要实现任何东西(你只需要
TypeNameHandling在你的应用程序中配置一次) - 您不需要在操作参数中使用属性
- 你不需要事先知道可能的参数类型:你只需要知道基本类型,并在参数中指定它(它可以是一个抽象类型,使多态性更明显)
- 该解决方案适用于大多数情况(1)而无需更改任何内容
- 该解决方案经过广泛测试和优化
- 你不需要魔法字符串
- 实现是通用的,将接受任何派生类型
(1): if you want to receive parameter values that don't inherit from the same base type, this will not work, but I see no point on doing so
(1):如果您想接收不是从同一基类型继承的参数值,这将不起作用,但我认为这样做没有意义
So I can't find any disadvantages, and find many advantages on JSON.NET solution.
所以我找不到任何缺点,并在 JSON.NET 解决方案上找到许多优点。
WHY USING CUSTOM JsonConverter/JsonConverterAttribute
为什么使用自定义 JsonConverter/JsonConverterAttribute
This is a good working solution that allows customization, that can be modified or extended to adapt it to your particular case.
这是一个很好的工作解决方案,允许自定义,可以修改或扩展以适应您的特定情况。
If you want to do something that the native solution cannot do, like customizing the type names, or inferring the type of the parameter based on available property names, then do use this solution adapted to your own case. The other one cannot be customized, and will not work for your needs.
如果您想做一些本机解决方案无法做到的事情,例如自定义类型名称,或根据可用的属性名称推断参数的类型,请使用适合您自己情况的解决方案。另一个不能定制,也不能满足您的需求。
回答by user8606451
If you want to use the TypeNameHandling.Auto but are concerned with security or dont like api consumers needing that level of behind the scenes knowledge you can handle the $type deserialize your self.
如果您想使用 TypeNameHandling.Auto 但担心安全性或不喜欢需要那种级别的幕后知识的 API 消费者,您可以处理 $type 反序列化您自己。
public class InheritanceSerializationBinder : DefaultSerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
switch (typeName)
{
case "parent[]": return typeof(Class1[]);
case "parent": return typeof(Class1);
case "child[]": return typeof(Class2[]);
case "child": return typeof(Class2);
default: return base.BindToType(assemblyName, typeName);
}
}
}
Then hook this up in global.asax.Application__Start
然后将其连接到 global.asax.Application__Start
var config = GlobalConfiguration.Configuration;
config.Formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings { Binder = new InheritanceSerializationBinder() };
finally i have used a wrapper class and [JsonProperty(TypeNameHandling = TypeNameHandling.Auto)] on a properpty containing the object with different types as i have not been able to get it to work by configuring the actual class.
最后,我在包含不同类型对象的属性上使用了包装类和 [JsonProperty(TypeNameHandling = TypeNameHandling.Auto)],因为我无法通过配置实际类来使其工作。
This approach allows consumers to include the needed information in their request while allowing the documentation of the allowable values to be platform independent, easy to change, and easy to understand. All without having to write your own converster.
这种方法允许消费者在他们的请求中包含所需的信息,同时允许允许值的文档与平台无关、易于更改且易于理解。所有这些都无需编写自己的转换器。
Credit to : https://mallibone.com/post/serialize-object-inheritance-with-json.netfor showing me the custom deserializer of that field property.
归功于:https: //mallibone.com/post/serialize-object-inheritance-with-json.net向我展示了该字段属性的自定义反序列化器。

