Json.Net Deserialization of Complex Objects - Base Class that Contains a Base Class as a Property

ぐ巨炮叔叔 提交于 2020-01-06 03:02:08

问题


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.


回答1:


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;
    


来源:https://stackoverflow.com/questions/36184162/json-net-deserialization-of-complex-objects-base-class-that-contains-a-base-cl

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!