Home > Software engineering >  Deserialize a object containing a nested json string with escaped characters to a nested POCO
Deserialize a object containing a nested json string with escaped characters to a nested POCO

Time:12-21

We are receiving json message which include an embedded json object with escaped characters.

As an example the escaped Json string rawMessage = "{\"lte\": \"{\\\"lst\\\":5,\\\"lrs\\\":19,\\\"lrq\\\":3}\"}";

This is obviously quick difficult to test with and has the potentially for countless typing errors, so we would like to find a cleaner way of working with POCOs rather than raw strings. Unfortunately deserializing the embedded message isn't straight forward.

The string above has an equivalent model of:

public class Message{
    public string lte { get; set;}
}

Using this in code we can do JsonConvert.DeserializeObject<Message>(rawMessage) which deserialzies the string to the Message model, and if we hover over lte in the debugger shows the embedded Json.

What I'd like to do is Deserialize to the full POCO which is:

public class Message{
    public Lte lte { get; set;}
}

public class Lte{
    public int lst { get; set;}
    public int lrs { get; set;}
    public int lrq { get; set;}
}

However this results in a casting exception when JsonConvert.DeserializeObject<Message>(rawMessage) is run.

Interestingly, if you Deserialize twice the embedded json converts ok. Going back to the string version of Message you can run:

var message = JsonConvert.DeserializeObject<Message>(rawMessage);
var embeddedMessage = JsonConvert.DeserializeObject<Lte>(message.lte);

To retrieve the embedded POCO.

Is this possible with one Deserialize call, maybe just missing some decorators?

CodePudding user response:

The solution would be to write a custom JsonConverter.

If I understood correctly, you are using Newtonsoft.Json. Here is a custom JSON converter for your model:

public class MessageConverter : JsonConverter<Message> {
    public override void WriteJson(JsonWriter writer, Message value, JsonSerializer serializer) {
        writer.WriteToken(JsonToken.StartObject);
        writer.WritePropertyName("lte");
        writer.WriteValue(JsonConvert.SerializeObject(value.lte));
        writer.WriteToken(JsonToken.EndObject);
    }

    public override Message ReadJson(JsonReader     reader,
                                     Type           objectType,
                                     Message        existingValue,
                                     bool           hasExistingValue,
                                     JsonSerializer serializer) {
        string? lteString = null;
        while (reader.Read()) {
            if (reader.TokenType == JsonToken.PropertyName && (string)reader.Value! == "lte") {
                lteString = reader.ReadAsString();
                break;
            }
        }
        while(reader.Read()) {}

        if (lteString is null) {
            throw new JsonException("lte property missing");
        }
        return new Message { lte = JsonConvert.DeserializeObject<Lte>(lteString) };
    }
}

Usage:

string rawMessage  = "{\"lte\": \"{\\\"lst\\\":5,\\\"lrs\\\":19,\\\"lrq\\\":3}\"}";
var    message     = JsonConvert.DeserializeObject<Message>(rawMessage, new MessageConverter());

UPDATE

You could also apply a [JsonConverter(typeof(MessageConverter))] to the Message class:

[JsonConverter(typeof(MessageConverter))]
public class Message {
    public Lte lte { get; set; }
}

string rawMessage  = "{\"lte\": \"{\\\"lst\\\":5,\\\"lrs\\\":19,\\\"lrq\\\":3}\"}";
var    message     = JsonConvert.DeserializeObject<Message>(rawMessage);

This way you wouldn't need to use new MessageConverter() every time.


UPDATE

I came up with a little simplier solution. Write a converter only for Lte and apply it directly in the Message class on the lte property via an attribute. Looks much cleaner and allows to extend Message class safely without the need to modify the converter:

public class Message {
    [JsonConverter(typeof(LteConverter))]
    public Lte lte { get; set; }
}

public class Lte {
    public int lst { get; set; }
    public int lrs { get; set; }
    public int lrq { get; set; }
}

public class LteConverter : JsonConverter<Lte> {
    public override void WriteJson(JsonWriter writer, Lte value, JsonSerializer serializer) {
        writer.WriteValue(JsonConvert.SerializeObject(value));
    }

    public override Lte ReadJson(JsonReader     reader,
                                 Type           objectType,
                                 Lte            existingValue,
                                 bool           hasExistingValue,
                                 JsonSerializer serializer) {
        return JsonConvert.DeserializeObject<Lte>((string)reader.Value!);
    }
}

Usage:

string rawMessage  = "{\"lte\": \"{\\\"lst\\\":5,\\\"lrs\\\":19,\\\"lrq\\\":3}\"}";
var    message     = JsonConvert.DeserializeObject<Message>(rawMessage);

CodePudding user response:

you can parse at first,after this you can deserialize

var lteJson = (string) JObject.Parse(rawMessage)["lte"];
Lte lte = JsonConvert.DeserializeObject<Lte>(lteJson);

or just in one line

Lte lte = JsonConvert.DeserializeObject<Lte>((string)JObject.Parse(rawMessage)["lte"]);
  • Related