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"]);