在 Asp.Net Web API 中将 JSON 反序列化为派生类型
Posted
技术标签:
【中文标题】在 Asp.Net Web API 中将 JSON 反序列化为派生类型【英文标题】:Deserialising JSON to derived types in Asp.Net Web API 【发布时间】:2012-09-20 06:19:47 【问题描述】:我正在调用我的 WebAPI 的一个方法,发送一个我想与模型匹配(或绑定)的 JSON。
在控制器中我有一个类似的方法:
public Result Post([ModelBinder(typeof(CustomModelBinder))]MyClass model);
作为参数给出的'MyClass'是一个抽象类。我希望根据传递的 json 类型,实例化正确的继承类。
为了实现它,我正在尝试实现一个自定义活页夹。问题是(我不知道它是否非常基本,但我找不到任何东西)我不知道如何检索请求中的原始 JSON(或者更好的是某种序列化)。
我明白了:
actionContext.Request.Content但所有方法都公开为异步。我不知道这适合将生成模型传递给控制器方法...
【问题讨论】:
【参考方案1】:您不需要自定义模型绑定器。您也不需要处理请求管道。
看看另一个 SO:How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?。
我以此作为我自己解决同一问题的基础。
从该 SO 中引用的 JsonCreationConverter<T>
开始(稍作修改以解决响应中类型序列化的问题):
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();
现在您可以使用 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;
现在您可以使用基本类型作为参数:
public Result Post(BaseClass arg)
如果我们要发帖:
typename: 'DerivedType', DerivedProperty: 'hello'
那么arg
将是DerivedClass
的一个实例,但如果我们发布:
DefaultProperty: 'world'
然后你会得到一个DefaultClass
的实例。
编辑 - 为什么我更喜欢这种方法而不是 TypeNameHandling.Auto/All
我确实相信使用 JotaBe 支持的 TypeNameHandling.Auto/All
并不总是理想的解决方案。在这种情况下很可能是这样 - 但我个人不会这样做,除非:
当使用 Json.Net TypeNameHandling.Auto
或 All
时,您的 Web 服务器将开始以 MyNamespace.MyType, MyAssemblyName
格式发送类型名称。
我在 cmets 中说过,我认为这是一个安全问题。在我从 Microsoft 阅读的一些文档中提到了这一点。它似乎不再被提及,但我仍然觉得这是一个有效的担忧。我永远不想向外界公开命名空间限定的类型名称和程序集名称。它增加了我的攻击面。所以,是的,我的 API 类型不能有Object
属性/参数,但谁能说我的网站的其余部分完全没有漏洞?谁说未来的端点不会暴露利用类型名称的能力?为什么要抓住这个机会,因为它更容易?
另外 - 如果您正在编写一个“适当的”API,即专门供第三方使用,而不仅仅是供您自己使用,并且您正在使用 Web API,那么您很可能希望利用 JSON/XML内容类型处理(至少)。看看您在尝试编写易于使用的文档方面取得了多大的成就,这些文档针对 XML 和 JSON 格式以不同的方式引用您的所有 API 类型。
通过重写 JSON.Net 对类型名称的理解方式,您可以使两者保持一致,为您的调用者在 XML/JSON 之间进行选择完全基于口味,而不是因为类型名称更容易记住一个或另一个。
【讨论】:
已在您的帖子中添加了一条评论,说明为什么您的解决方案虽然正确,但不应真正“在野外”使用。 为了表明我不只是一个混蛋——我已经 +1 了你的答案,因为它在一定程度上是有效的——并且很可能是最简单的方法问题。我只是不认为它在所有情况下都应该被视为灵丹妙药。 我已经更新了我的答案,以反映您的解决方案何时是最佳选择。我认为这是一次非常有建设性的讨论。您也有我的 +1,因为您的解决方案是对 JSON 反序列化过程进行各种自定义的完美起点。而现在,这两个答案都得到了改善。 只是为了添加注释。过去我使用了 $type 解决方案,但是对于我现在正在使用 AngularJS 进行的项目,它在 json 中用 $ 剥离任何东西时存在问题。此外,使用 TypeScript 类,我无法弄清楚如何确保 $type 包含在 JSON 序列化中。我确信这些问题有解决方案,但这种方法让我能够轻松克服这些问题。 我对 $type 最大的抱怨是我不想在重构服务器端代码时破坏 API 兼容性,例如重命名类型或将其移动到另一个命名空间或程序集。例如,存储在客户端存储中并在以后使用的 DTO 将突然不再工作。如果改为使用枚举值或字符串,则可以完全控制 API 的向后兼容性。【参考方案2】:您不需要自己实现它。 JSON.NET 对它有原生支持。
您必须为 JSON 格式化程序指定 desired TypeNameHandling option,如下所示(在 global.asax
应用程序启动事件中):
JsonSerializerSettings serializerSettings = GlobalConfiguration.Configuration
.Formatters.JsonFormatter.SerializerSettings;
serializerSettings.TypeNameHandling = TypeNameHandling.Auto;
如果您指定Auto
,就像上面的示例一样,参数将被反序列化为对象的$type
属性中指定的类型。如果$type
属性缺失,它将被反序列化为参数的类型。因此,您只需在传递派生类型的参数时指定类型。 (这是最灵活的选项)。
例如,如果您将此参数传递给 Web API 操作:
var param =
$type: 'MyNamespace.MyType, MyAssemblyName', // .NET fully qualified name
... // object properties
;
参数将被反序列化为MyNamespace.MyType
类的对象。
这也适用于子属性,即你可以有一个像这样的对象,它指定一个内部属性是给定类型的
var param =
myTypedProperty:
$type: `...`
...
;
在这里你可以看到sample on JSON.NET documentation of TypeNameHandling.Auto。
This works at least since JSON.NET 4 release.
注意
你不需要用属性来装饰任何东西,或者做任何其他的定制。它无需更改您的 Web API 代码即可工作。
重要提示
The $type must be the first property of the JSON serialized object。如果不是,它将被忽略。
与自定义 JsonConverter/JsonConverterAttribute 的比较
我在比较原生解决方案to this answer。
实现JsonConverter
/JsonConverterAttribute
:
JsonConverter
,以及自定义JsonConverterAttribute
你需要使用属性来标记参数
您需要事先了解参数预期的可能类型
只要您的类型或属性发生变化,您就需要实现或更改JsonConverter
的实现
有magic strings 的代码味道,表示预期的属性名称
您没有实现可用于任何类型的通用内容
你正在重新发明***
在答案的作者中有一条关于安全的评论。除非你做错了什么(比如为你的参数接受一个过于通用的类型,比如Object
),否则不会有获得错误类型实例的风险:JSON.NET 本机解决方案只实例化参数类型的对象,或者派生自它的类型(如果不是,你会得到null
)。
而这些是 JSON.NET 原生解决方案的优势:
你不需要实现任何东西(你只需要在你的应用中配置一次TypeNameHandling
)
您无需在操作参数中使用属性
您不需要事先知道可能的参数类型:您只需要知道基本类型,并在参数中指定它(可以是抽象类型,使多态性更加明显)
该解决方案适用于大多数情况(1),无需更改任何内容
此解决方案经过广泛测试和优化
你不需要魔术字符串
实现是通用的,可以接受任何派生类型
(1):如果你想接收不继承自相同基类型的参数值,这将不起作用,但我认为这样做没有意义
所以我找不到任何缺点,并在 JSON.NET 解决方案上找到了许多优点。
为什么要使用自定义 JsonConverter/JsonConverterAttribute
这是一个很好的工作解决方案,允许自定义,可以修改或扩展以适应您的特定情况。
如果你想做一些原生解决方案无法做到的事情,比如自定义类型名称,或者根据可用的属性名称推断参数的类型,那么请使用适合你自己情况的解决方案。另一个无法自定义,无法满足您的需求。
【讨论】:
是的,你是对的 - 但这是不适合我的情况(包括我的情况)的原因。使用 Json.Net 的内置类型名称处理功能存在深 安全问题,因为它有效地允许恶意调用者绑定您的任何类型 - 或任何 .Net 类型。该文档对此进行了具体说明。我的解决方案提供了一个抽象,您可以精确控制可以绑定的类型。 JotaBe - 在没有首先考虑到这是一个适当缩减的答案的情况下,要非常小心地小跑“不通用”、“没有经过良好测试”和“可能性能较差”的短语,以免提供一堵代码墙,但很容易测试和扩展。其次,我基于此的解决方案是在非常繁忙的 Web API 环境中实现的更广泛的解决方案,它表现出色。是的 -$type
的东西在那里,并且有效。将安全问题视为“不是用例”是幼稚的。你也应该带着SerializationBinder
来回答我的观点。
Web API 文档中很早就提到了对安全性的担忧——而且,是的,谷歌似乎已经放弃了这一点。公平的做法;但我不是骗子,我确实读过这些担忧。即使没有其他人支持 - 我个人不想在我的 JSON 中编码 MyNamespace.MyType, MyAssembly
类型名称 - 我不想将我的真实类型名称任何暴露给外界;当一个简单的TypeName
就足够时,通过 JSON 使用我的 API 的第三方也不想使用 .Net 类型名称。不过,每个人都有自己的特点。
我看到的唯一安全问题是使用通用参数类型,如对象或动态,这是唯一允许实例化任意对象的情况。我只是想知道我是否还缺少其他东西。嘿!,我不是想看不起你的解决方案,或者认为你在撒谎,一点也不:我想表达一个公平的意见。我真的认为 JSON.NET 解决方案更通用,开箱即用(只需要一个参数基类),经过广泛的现场测试(在 Nuget 中有近 6M 的下载量),这比任何经过深思熟虑的单元测试套件都可以进行更多测试实施
好吧,那么您应该公平地说,TypeNameHandling
工作正常,这是一个很好的解决方案除非存在无法处理的问题。在这种情况下,您提供了一个经过测试的解决方案,该解决方案展示了如何进行反序列化的自定义实现,这对于需要特殊自定义的许多其他人很有用(例如,在您的情况下,您不想使用 .NET 完全限定名称) .我同意这一点!!对于这些情况,这是一个很好的解决方案(而且一点也不明显)。但是原生解决方案对大多数人来说仍然很好。【参考方案3】:
您可以正常调用异步方法,您的执行将被暂停,直到方法返回,您可以以标准方式返回模型。只需像这样拨打电话:
string jsonContent = await actionContext.Request.Content.ReadAsStringAsync();
它将为您提供原始 JSON。
【讨论】:
除非您对可能出现的死锁感到满意,否则不要使用“.Result”nitoprograms.blogspot.ch/2012/07/dont-block-on-async-code.html【参考方案4】:如果您想使用 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);
然后将其连接到 global.asax.Application__Start
var config = GlobalConfiguration.Configuration;
config.Formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings Binder = new InheritanceSerializationBinder() ;
最后,我在包含不同类型对象的属性上使用了包装类和 [JsonProperty(TypeNameHandling = TypeNameHandling.Auto)],因为我无法通过配置实际类来使其工作。
这种方法允许消费者在他们的请求中包含所需的信息,同时允许允许值的文档独立于平台、易于更改且易于理解。所有这些都无需编写您自己的转换器。
归功于:https://mallibone.com/post/serialize-object-inheritance-with-json.net 向我展示了该字段属性的自定义反序列化器。
【讨论】:
以上是关于在 Asp.Net Web API 中将 JSON 反序列化为派生类型的主要内容,如果未能解决你的问题,请参考以下文章
如何在 ASP.NET Web API 实现中将数组传递给 OData 函数?
Asp.Net Web API 2第十三课——ASP.NET Web API中的JSON和XML序列化
如何在 ASP.NET Web API 中为 Json.NET 设置自定义 JsonSerializerSettings?