0

I have an application/json response from an API that has a property, itself, containing an escaped JSON string.

{
    "id": 0,
    "aggregation_id": "533741f4-49da-4db9-9660-4ca7bafb30e1",
    "task_id": "217",
    "event_type": "discovery",
    "event_name": "device_discovery_complete",
    "method": "ssh",
    "message_details": "{\"aggregation_id\":\"533741f4-49da-4db9-9660-4ca7bafb30e1\",\"ou_id\":0,\"device_id\":13,\"node_id\":13,\"task_id\":217}",
    "time": "2018-01-25T17:59:25"
  }

I want to deserialize the object and the inner object to a model type.

public class Response
{
    public DateTime time {get; set;}
    public string event_name {get; set;}
    public string event_type {get; set;}
    public string method {get; set;}
    public MessageDetails message_details {get; set;}
}

public class MessageDetails
{
    public int device_id {get; set;}
}

Using a call like this

JsonConvert.DeserializeObject<Response>("... response string...");

However, Netwonsoft.Json handles the outer properties just fine, but throws an exception on matching message_details.

Newtonsoft.Json.JsonSerializationException: Error converting value "... response string snipped ..." to type 'RpcApi.Entities.MessageDetails'. 
Path '[0].message_details', line 1, position 390. 
---> System.ArgumentException: Could not cast or convert from System.String to RpcApi.Entities.MessageDetails.
Anthony Mastrean
  • 21,850
  • 21
  • 110
  • 188
  • have you tried removing the \ from the serialized json before converting? – cptwonton Jan 25 '18 at 18:21
  • I'm hoping to _avoid_ pre-processing the response string, if at all possible. If there's some way to _tell_ Newtonsoft.Json how to handle a nested JSON string. – Anthony Mastrean Jan 25 '18 at 18:39
  • Your problem is that `"message_details"` is actually serialized JSON embedded as a string. If you can't fix the incoming JSON, you could use `EmbeddedLiteralConverter` from [C# - How can I convert escape string JSON values within a JSON object into literal JSON?](https://stackoverflow.com/a/39154630/3744182). – dbc Jan 25 '18 at 18:44
  • *I'm hoping to avoid pre-processing*, well, you either do pre-processing or post-processing. There is not other way. For post-processing, you make the `MessageDetails` a string, add another property like `MessageDetailsDeserialized`, deserialize the object, and then deserialize `MessageDetails` to `MessageDetailsDeserialized`. – Racil Hilan Jan 25 '18 at 18:44
  • Aw, how is my question marked as a duplicate of a question with negative points?! I'm asking from the perspective of end-to-end type serialization, that other question is asking about sanitizing the input string _first_. Perspective matters! – Anthony Mastrean Jan 29 '18 at 15:00

1 Answers1

2

You could use a custom JsonConverter for this similar to this:

public class EmbeddedJsonConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return serializer.Deserialize(new StringReader((string)reader.Value), objectType);
    }

    public override bool CanConvert(Type objectType)
    {
        return true;
    }
}

Mark the property with [JsonConverter(typeof(EmbeddedJsonConverter))] like:

public class Response
{
    public DateTime time { get; set; }
    public string event_name { get; set; }
    public string event_type { get; set; }
    public string method { get; set; }

    [JsonConverter(typeof(EmbeddedJsonConverter))]
    public MessageDetails message_details { get; set; }
}

Then you will be able to deserialize with JsonConvert.DeserializeObject<Response>().

The EmbeddedJsonConverter class extracts the json string from the object and then deserializes it. CanConvert should probably be made smarter for a truly generic use.

George Richardson
  • 1,228
  • 12
  • 19