如何在JSON对象中转换转义的JSON字符串

本文关键字:JSON 转义 字符串 转换 对象 | 更新日期: 2023-09-27 18:04:06

我正在从一个公共API接收一个JSON对象,其属性本身是一个转义的JSON字符串。

{
   "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}}"
      }
   ]
}

如何将此属性转换为实际的JSON对象(未转义),以便使用NewtonSoft Json.NET反序列化整个响应?

如何在JSON对象中转换转义的JSON字符串

您的JSON包含"body"对象的文字字符串,该字符串实际上是嵌入的双序列化JSON。要将其反序列化为POCO层次结构,而不需要在任何类型中引入中间的string Json代理属性,您有以下几个选项:

  1. 你可以使用LINQ对JSON进行预处理,并将文字"body"字符串替换为解析后的等效字符串:

        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>();
    
  2. 您可以为POCO引入一个通用的JsonConverter,对应于每个Body对象,该对象将传入的嵌入式JSON字符串文字反序列化为字符串,然后将该字符串反序列化为最终的POCO:

    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();
        }
    }
    

    然后像这样使用:

    var root = JsonConvert.DeserializeObject<RootObject>(json, new EmbeddedLiteralConverter<Body>());
    

    或者将其应用于"body"对应的属性,如下所示:

    public class Response
    {
        public string info { get; set; }
        [JsonConverter(typeof(EmbeddedLiteralConverter<Body>))]
        public Body body { get; set; }
    }
    

    注意,转换器检查传入的JSON令牌是否为字符串,如果不是,则直接反序列化。因此,当"body" JSON是或不是双序列化时,转换器应该是可用的。

出于测试目的,我使用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 Response
{
    public string info { get; set; }
    public Body body { get; set; }
}
public class RootObject
{
    public List<Response> responses { get; set; }
}

要将json编码为json字符串转换为Jobject,您总是可以使用下面的技术,

var token = JToken.Parse(text);
var json = JObject.Parse((string) token);
  1. 你可以将它反序列化成一个中间类,它有一个属性:string Body {get; set;}
  2. 将"body"字符串反序列化为合适的类型
  3. 创建一个代表目标模型的类的新实例。
  4. 序列化该模型

下面是一个使用动态类型和匿名对象实现这一点的程序。

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);
}

或者,如果您不想重新序列化对象,则可以构造目标类型的新版本,而不是匿名类型。

这是我基于Sam I am的答案使用的可行解决方案:

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