问题
I'm receiving a JSON object from a public API with a property that, itself, is an escaped JSON string.
{
"responses":[
{
"info":"keep \"this\" in a string",
"body":"{\"error\":{\"message\":\"Invalid command\",\"type\":\"Exception\",\"code\":123}}"
},
{
"info":"more \"data\" to keep in a string",
"body":"{\"error\":{\"message\":\"Other error\",\"type\":\"Exception\",\"code\":321}}"
}
]
}
How do I convert this property into an actual JSON object (unescaped) in order to deserialize the entire response using NewtonSoft Json.NET?
回答1:
Your JSON contains a literal string for the "body"
objects that is actually embedded, double-serialized JSON. To deserialize this to a POCO hierarchy without needing to introduce an intermediate string Json
surrogate property inside any of your types, you have a few options:
You could preprocess your JSON using LINQ to JSON and replace the literal
"body"
strings with their parsed equivalents:var rootToken = JToken.Parse(json); foreach (var token in rootToken.SelectTokens("responses[*].body").ToList().Where(t => t.Type == JTokenType.String)) { token.Replace(JToken.Parse((string)token)); } var root = rootToken.ToObject<RootObject>();
You could introduce a generic
JsonConverter
for the POCO corresponding to eachBody
object that parses the incoming embedded JSON string literal into aLINQ to JSON
hierarchy, then deserializes that:public class EmbeddedLiteralConverter<T> : JsonConverter { public override bool CanConvert(Type objectType) { return typeof(T).IsAssignableFrom(objectType); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) return null; var contract = serializer.ContractResolver.ResolveContract(objectType); if (contract is JsonPrimitiveContract) throw new JsonSerializationException("Invalid type: " + objectType); if (existingValue == null) existingValue = contract.DefaultCreator(); if (reader.TokenType == JsonToken.String) { var json = (string)JToken.Load(reader); using (var subReader = new JsonTextReader(new StringReader(json))) { // By populating a pre-allocated instance we avoid an infinite recursion in EmbeddedLiteralConverter<T>.ReadJson() // Re-use the existing serializer to preserve settings. serializer.Populate(subReader, existingValue); } } else { serializer.Populate(reader, existingValue); } return existingValue; } public override bool CanWrite { get { return false; } } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } }
Then use it like:
var root = JsonConvert.DeserializeObject<RootObject>(json, new EmbeddedLiteralConverter<Body>());
Note that the converter checks to see whether the incoming JSON token is a string, and if not, deserializes directly. Thus the converter should be usable when the
"body"
JSON is and is not double-serialized.
For testing purposes I generated the following target classes using http://json2csharp.com/:
public class Error
{
public string message { get; set; }
public string type { get; set; }
public int code { get; set; }
}
public class Body
{
public Error error { get; set; }
}
public class Respons
{
public string info { get; set; }
public Body body { get; set; }
}
public class RootObject
{
public List<Respons> responses { get; set; }
}
回答2:
- you can deserialize it into an intermediary class that has a property:
string Body {get; set;}
- deserialize the "body" string into it's appropriate type
- create a new instance of your a class that represents your destination model.
- serialize that model
Here's a program that does it with the dynamic type and anonymous objects.
static void Main(string[] args)
{
var json = File.ReadAllText("JsonFile1.json");
dynamic obj = JsonConvert.DeserializeObject(json);
var dest = new
{
responses = ((IEnumerable<dynamic>)obj.responses).Select(x => new
{
info = x.info,
body = JsonConvert.DeserializeObject((string)x.body)
})
};
var destJson = JsonConvert.SerializeObject(dest);
File.WriteAllText("JsonFile2.json", destJson);
}
Alternatively, you can just construct a new version of whatever your destination type is instead of an anonymous type, if you don't feel like reserializing the josn.
回答3:
Here's the workable solution I used based off of Sam I am's answer:
dynamic obj = JsonConvert.DeserializeObject(json);
foreach (var response in (IEnumerable<dynamic>)obj.responses)
{
response.body = JsonConvert.DeserializeObject((string)response.body);
}
string result = JsonConvert.SerializeObject(obj);
回答4:
To convert a json encoded as json string to Jobject, you can always use the technique below,
var token = JToken.Parse(text);
var json = JObject.Parse((string) token);
来源:https://stackoverflow.com/questions/51802068/how-to-escape-especial-characters-in-json