首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >在Asp.Net Web API中将JSON反序列化为派生类型

在Asp.Net Web API中将JSON反序列化为派生类型
EN

Stack Overflow用户
提问于 2012-09-28 19:01:11
回答 4查看 49.8K关注 0票数 63

我正在调用我的WebAPI的一个方法,发送一个我想要与模型匹配(或绑定)的JSON。

在控制器中,我有一个类似如下的方法:

代码语言:javascript
复制
public Result Post([ModelBinder(typeof(CustomModelBinder))]MyClass model);

‘'MyClass’是一个抽象类,它是作为参数给出的。我希望根据传递的json类型,实例化正确的继承类。

为了实现它,我正在尝试实现一个自定义绑定器。问题是(我不知道这是不是很基本,但是我找不到任何东西),我不知道如何检索请求中的原始JSON (或者更好,某种序列化)。

我明白了:

  • actionContext.Request.Content

但是所有的方法都是异步的。我不知道这适合谁将生成模型传递给控制器方法……

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2012-09-28 22:03:04

您不需要自定义模型绑定器。您也不需要在请求流水线上胡乱操作。

看看另一个SO:How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?

我将此作为我自己解决相同问题的基础。

从SO中引用的JsonCreationConverter<T>开始(稍微修改一下,以修复响应中类型序列化的问题):

代码语言:javascript
复制
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指向自定义转换器:

代码语言:javascript
复制
[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; }
}

现在,您可以将基类型用作参数:

代码语言:javascript
复制
public Result Post(BaseClass arg) {

}

如果我们要发布:

代码语言:javascript
复制
{ typename: 'DerivedType', DerivedProperty: 'hello' }

那么arg将是DerivedClass的一个实例,但是如果我们发布:

代码语言:javascript
复制
{ DefaultProperty: 'world' }

然后,您将获得DefaultClass的一个实例。

编辑-为什么我更喜欢这种方法而不是TypeNameHandling.Auto/All

我确实认为,使用JotaBe所支持的TypeNameHandling.Auto/All并不总是理想的解决方案。在这种情况下很可能是这样--但就我个人而言,我不会这么做,除非:

  • 我的API仅供我或我的team
  • 使用我不关心是否有一个双XML兼容的端点

当使用Json.Net TypeNameHandling.AutoAll时,您的web服务器将开始以MyNamespace.MyType, MyAssemblyName格式发送类型名称。

我在评论中说过,我认为这是一个安全问题。我从微软读到的一些文档中提到了这一点。这似乎不再被提及,但我仍然觉得这是一个合理的担忧。我不想想要向外界公开命名空间限定的类型名称和程序集名称。它增加了我的攻击面。所以,是的,我的API类型不能有Object属性/参数,但是谁能保证我站点的其余部分是完全没有漏洞的呢?谁能说未来的端点不会暴露利用类型名称的能力呢?为什么仅仅因为它更容易就冒这个险呢?

此外,如果您正在编写一个“适当的”API,即专门供第三方使用,而不是仅供您自己使用,并且您正在使用Web API,那么您最有可能希望利用JSON/XML内容类型处理(作为最低要求)。看看您在编写易于使用的文档方面取得了多大进展,这些文档针对XML和JSON格式以不同的方式引用您的所有API类型。

通过覆盖JSON.Net理解类型名称的方式,您可以将两者结合起来,让调用者完全根据喜好在XML/JSON之间做出选择,而不是因为两者中的类型名称更容易记住。

票数 94
EN

Stack Overflow用户

发布于 2014-06-03 00:23:06

你不需要自己去实现它。JSON.NET对它有本机支持。

您必须为JSON格式化程序指定desired TypeNameHandling option,如下所示(在global.asax应用程序启动事件中):

代码语言:javascript
复制
JsonSerializerSettings serializerSettings = GlobalConfiguration.Configuration
   .Formatters.JsonFormatter.SerializerSettings;
serializerSettings.TypeNameHandling = TypeNameHandling.Auto;

如果像上面的示例一样指定Auto,参数将被反序列化为在对象的$type属性中指定的类型。如果缺少$type属性,它将被反序列化为参数的类型。因此,您只需在传递派生类型的参数时指定类型。(这是最灵活的选项)。

例如,如果将此参数传递给Web API操作:

代码语言:javascript
复制
var param = {
    $type: 'MyNamespace.MyType, MyAssemblyName', // .NET fully qualified name
    ... // object properties
};

该参数将被反序列化为MyNamespace.MyType类的对象。

这也适用于子属性,也就是说,你可以有一个这样的对象,它指定一个内部属性是一个给定的类型

代码语言:javascript
复制
var param = { 
   myTypedProperty: {
      $type: `...`
      ...
};

在这里您可以看到一个sample on JSON.NET documentation of TypeNameHandling.Auto

This works at least since JSON.NET 4 release

笔记

你不需要用attirbutes来装饰任何东西,也不需要做任何其他的定制。它不需要对您的Web API代码进行任何更改即可工作。

重要说明

The $type must be the first property of the JSON serialized object。如果不是,它将被忽略。

与自定义JsonConverter/JsonConverterAttribute的比较

我正在比较本机解决方案to this answer

实现JsonConverter/JsonConverterAttribute

自定义代码您需要实现自定义属性,而自定义JsonConverterAttribute

  • you需要使用属性来标记parameters

  • you需要预先知道parameter

  • you可能需要实现的类型,或者更改JsonConverter的实现每当您的类型或属性发生更改时

  • 有一种magic strings的代码味道,以指示期望的属性名称

  • 您没有实现可以与任何类型一起使用的泛型<

  • >H237JsonConverter >< code >H138您正在重新发明轮子

在答案的作者中,有一条关于安全性的评论。除非您做了一些错误的事情(如接受参数的过于泛型的类型,如Object),否则不会有获得错误类型的实例的风险: JSON.NET本机解决方案只实例化参数类型的对象或从该对象派生的类型(如果不是这样,您将获得null)。

以下是JSON.NET本机解决方案的优势:

  • 你不需要实现任何东西(你只需要在你的应用程序中配置一次TypeNameHandling )
  • 你不需要在你的动作parameters
  • you中使用属性不需要事先知道可能的参数类型:你只需要知道基本类型,并在参数中指定它(它可以是抽象类型,以使多态性更明显)
  • 该解决方案适用于大多数情况(1)无需更改操作解决方案经过广泛测试,和optimized
  • you不需要魔术字符串
  • 实现是泛型的,可以接受任何派生类型

(1):如果您希望接收不是从同一基类型继承的参数值,这将不起作用,但我认为这样做没有意义

所以我找不到任何缺点,却发现JSON.NET解决方案有很多优点。

使用自定义JsonConverter/JsonConverterAttribute原因

这是一个很好的工作解决方案,允许自定义,可以修改或扩展以适应您的特定情况。

如果您想做一些本机解决方案不能做的事情,比如自定义类型名称,或者根据可用的属性名称推断参数的类型,那么一定要根据您自己的情况使用此解决方案。另一个不能定制,不能满足你的需求。

票数 49
EN

Stack Overflow用户

发布于 2012-09-28 20:39:46

你可以正常地调用异步方法,你的执行将被简单地挂起,直到方法返回,你可以以标准的方式返回模型。就像这样打个电话:

代码语言:javascript
复制
string jsonContent = await actionContext.Request.Content.ReadAsStringAsync();

它将为您提供原始的JSON。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/12638741

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档