Json.Net复杂对象的反序列化-包含基类作为属性的基类 [英] Json.Net Deserialization of Complex Objects - Base Class that Contains a Base Class as a Property

查看:76
本文介绍了Json.Net复杂对象的反序列化-包含基类作为属性的基类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

几个小时以来,我一直在尝试解决此问题.我尝试了许多方法并阅读了许多文章.我似乎找不到解决方法.

我有一个对象层次结构,由此我具有包含另一个派生类型的派生类型.我正在使用Json.Net的TypeNameHandling设置.在序列化和反序列化方面,我都将其设置为Auto.

生成的JSON是:

"[\r\n  {\r\n    \"$type\": \"Common.Monitors.HeartbeatMonitor, Common\",\r\n    \"ClassName\": \"HeartbeatMonitor\",\r\n    \"ServerGroupMonitorId\": 1,\r\n    \"Instructions\": {\r\n      \"$type\": \"Common.Monitors.HeartbeatMonitorInstructions, Common\",\r\n      \"RunIntervalInMinutes\": 1\r\n    },\r\n    \"LastExecutionDateTime\": \"2016-03-22T16:35:18.7458519\"\r\n  },\r\n  {\r\n    \"$type\": \"Common.Monitors.DiskSpaceMonitor, Common\",\r\n    \"ClassName\": \"DiskSpaceMonitor\",\r\n    \"ServerGroupMonitorId\": 2,\r\n    \"Instructions\": {\r\n      \"$type\": \"Common.Monitors.DiskSpaceMonitorInstructions, Common\",\r\n      \"DriveLetter\": \"C:\\\",\r\n      \"AlertWhenPercentFreeLessThan\": 20,\r\n      \"RunIntervalInMinutes\": 30\r\n    },\r\n    \"LastExecutionDateTime\": \"2016-03-22T16:15:18.7458519\"\r\n  }\r\n]"

尝试使用以下方法反序列化时:

string json = response.Content.ReadAsStringAsync().Result;

IEnumerable<MonitorBase> monitors = JsonConvert.DeserializeObject<IEnumerable<MonitorBase>>(json, new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Auto
            });

我收到以下异常:

Newtonsoft.Json.dll中发生了'Newtonsoft.Json.JsonSerializationException'类型的异常,但未在用户代码中处理.

其他信息:转换值时出错

"[
   {
     "$type": "Common.Monitors.HeartbeatMonitor, Common",
     "ClassName": "HeartbeatMonitor",
     "ServerGroupMonitorId": 1,
     "Instructions": {
       "$type": "Common.Monitors.HeartbeatMonitorInstructions, Common",
       "RunIntervalInMinutes": 1
     },
     "LastExecutionDateTime": "2016-03-22T16:35:18.7458519"
   },
   {
     "$type": "Common.Monitors.DiskSpaceMonitor, Common",
     "ClassName": "DiskSpaceMonitor",
     "ServerGroupMonitorId": 2,
     "Instructions": {
       "$type": "Common.Monitors.DiskSpaceMonitorInstructions, Common",
       "DriveLetter": "C:\\",
       "AlertWhenPercentFreeLessThan": 20,
       "RunIntervalInMinutes": 30
     },
     "LastExecutionDateTime": "2016-03-22T16:15:18.7458519"
   }
]" 

键入"System.Collections.Generic.IEnumerable`1 [Common.Monitors.MonitorBase]". 路径",第1行,位置943.

HeartbeatMonitorDiskSpaceMonitor类型是从MonitorBase派生的,它们各自的Instructions类型是从MonitorInstructionBase派生的.

我忍不住想起我遗漏了一些明显的东西,因为我无法想象这不是一个很常见的情况.

解决方案

我认为问题可能在于您的JSON是双重序列化的,如转义引号\",可见的换行符\r\n和引用整个JSON的事实.反序列化双序列化JSON时,结果是字符串,而不是对象,该字符串显然不能转换为任何类型的IEnumerable<T>.因此是错误.

如果您控制JSON的来源,请确保仅序列化对象一次.造成此问题的一个常见原因是未意识到某些框架(例如Web API)会自动为您进行序列化,因此,如果在返回对象之前先手动序列化对象,那么最终将得到双序列化JSON. /p>

如果您不控制JSON的来源,和/或无法让所有者修复它,则可以在客户端通过对JSON进行反序列化两次来对其进行更正:首先获取真实的" JSON字符串,然后第二个从中产生您的对象.例如:

string escapedJson = response.Content.ReadAsStringAsync().Result;

string realJson = JsonConvert.DeserializeObject<string>(escapedJson);

IEnumerable<MonitorBase> monitors = 
    JsonConvert.DeserializeObject<IEnumerable<MonitorBase>>(realJson, 
        new JsonSerializerSettings
        {
            TypeNameHandling = TypeNameHandling.Auto
        });

编辑

我从您的评论中看到,您正在手动序列化对象以利用Json.Net的TypeNameHandling设置,并且您正在寻找一种避免双重序列化的方法.

您基本上有两个选择:

  1. TypeNameHandling设置添加到Web API的全局序列化设置中,然后从您的方法中删除手动序列化代码.为此,请在global.asax中的Application_Start()方法中添加以下内容.

    var settings = GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings;
    settings.TypeNameHandling = TypeNameHandling.Auto;
    

  2. 或者,您可以更改Web API方法以创建并返回带有包含JSON的StringContent对象并将mediaType设置为application/jsonHttpResponseMessage.这样可以防止Web API尝试重新序列化结果.

    string json = JsonConvert.SerializeObject(monitors, new JsonSerializerSettings 
    {
        TypeNameHandling = TypeNameHandling.Auto,
        Formatting = Formatting.Indented
    }); 
    HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
    response.Content = new StringContent(json, Encoding.UTF8, "application/json");
    return response;
    

I have been attempting to resolve this issue for several hours. I have tried many approaches and read many posts. I can't seem to find a resolution.

I have an object hierarchy whereby I have derived types that contain another derived type. I am using Json.Net's TypeNameHandling setting. I have it set to Auto on both serialization and deserialization.

The resulting JSON is:

"[\r\n  {\r\n    \"$type\": \"Common.Monitors.HeartbeatMonitor, Common\",\r\n    \"ClassName\": \"HeartbeatMonitor\",\r\n    \"ServerGroupMonitorId\": 1,\r\n    \"Instructions\": {\r\n      \"$type\": \"Common.Monitors.HeartbeatMonitorInstructions, Common\",\r\n      \"RunIntervalInMinutes\": 1\r\n    },\r\n    \"LastExecutionDateTime\": \"2016-03-22T16:35:18.7458519\"\r\n  },\r\n  {\r\n    \"$type\": \"Common.Monitors.DiskSpaceMonitor, Common\",\r\n    \"ClassName\": \"DiskSpaceMonitor\",\r\n    \"ServerGroupMonitorId\": 2,\r\n    \"Instructions\": {\r\n      \"$type\": \"Common.Monitors.DiskSpaceMonitorInstructions, Common\",\r\n      \"DriveLetter\": \"C:\\\",\r\n      \"AlertWhenPercentFreeLessThan\": 20,\r\n      \"RunIntervalInMinutes\": 30\r\n    },\r\n    \"LastExecutionDateTime\": \"2016-03-22T16:15:18.7458519\"\r\n  }\r\n]"

When attempting to deserialize using the following:

string json = response.Content.ReadAsStringAsync().Result;

IEnumerable<MonitorBase> monitors = JsonConvert.DeserializeObject<IEnumerable<MonitorBase>>(json, new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Auto
            });

I receive the following exception:

An exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll but was not handled in user code.

Additional information: Error converting value

"[
   {
     "$type": "Common.Monitors.HeartbeatMonitor, Common",
     "ClassName": "HeartbeatMonitor",
     "ServerGroupMonitorId": 1,
     "Instructions": {
       "$type": "Common.Monitors.HeartbeatMonitorInstructions, Common",
       "RunIntervalInMinutes": 1
     },
     "LastExecutionDateTime": "2016-03-22T16:35:18.7458519"
   },
   {
     "$type": "Common.Monitors.DiskSpaceMonitor, Common",
     "ClassName": "DiskSpaceMonitor",
     "ServerGroupMonitorId": 2,
     "Instructions": {
       "$type": "Common.Monitors.DiskSpaceMonitorInstructions, Common",
       "DriveLetter": "C:\\",
       "AlertWhenPercentFreeLessThan": 20,
       "RunIntervalInMinutes": 30
     },
     "LastExecutionDateTime": "2016-03-22T16:15:18.7458519"
   }
]" 

to type 'System.Collections.Generic.IEnumerable`1[Common.Monitors.MonitorBase]'. Path '', line 1, position 943.

The HeartbeatMonitor and DiskSpaceMonitor types derive from MonitorBase and their respective Instructions types derive from MonitorInstructionBase.

I can't help but assume that I am missing something obvious as I can't imagine that this isn't a rather common scenario.

解决方案

I think that the problem might be that your JSON is double-serialized, as evidenced by the escaped quote marks \", the visible newlines \r\n and the fact that the whole JSON is quoted. When you deserialize double-serialized JSON, the result is a string, not an object, which clearly cannot be converted to an IEnumerable<T> of any kind. Hence the error.

If you control the source of the JSON, be sure that you are serializing your objects only once. A common cause of the problem is in not realizing that some frameworks, such as Web API, do serialization automatically for you, so if you are serializing your objects manually first before returning them then you'll end up with double-serialized JSON.

If you do not control the source of the JSON, and/or you cannot get the owner to fix it, then you can correct for it on the client side by deserializing the JSON twice: first to get the "real" JSON string, then second to produce your objects from that. For example:

string escapedJson = response.Content.ReadAsStringAsync().Result;

string realJson = JsonConvert.DeserializeObject<string>(escapedJson);

IEnumerable<MonitorBase> monitors = 
    JsonConvert.DeserializeObject<IEnumerable<MonitorBase>>(realJson, 
        new JsonSerializerSettings
        {
            TypeNameHandling = TypeNameHandling.Auto
        });

EDIT

I see from your comment that you are manually serializing your object in order to make use of Json.Net's TypeNameHandling setting, and you're looking for a way to avoid the double serialization.

You basically have two options:

  1. Add the TypeNameHandling setting to Web API's global serialization settings and remove the manual serialization code from your method. To do this, add the following in the Application_Start() method in your global.asax.

    var settings = GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings;
    settings.TypeNameHandling = TypeNameHandling.Auto;
    

  2. Alternatively, you can change your Web API method to create and return an HttpResponseMessage with a StringContent object containing your JSON and with the mediaType set to application/json. This should prevent Web API from trying to re-serialize the result.

    string json = JsonConvert.SerializeObject(monitors, new JsonSerializerSettings 
    {
        TypeNameHandling = TypeNameHandling.Auto,
        Formatting = Formatting.Indented
    }); 
    HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
    response.Content = new StringContent(json, Encoding.UTF8, "application/json");
    return response;
    

这篇关于Json.Net复杂对象的反序列化-包含基类作为属性的基类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆