【问题标题】:How do I convert an escaped JSON string within a JSON object?如何在 JSON 对象中转换转义的 JSON 字符串?
【发布时间】:2017-01-02 09:40:27
【问题描述】:

我从一个公共 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?它已经在 J​​SON 中
  • 我尝试过使用 Newtonsoft Json.NET 进行反序列化,但在尝试将主体转换为类时出现错误。 "Error converting value [JSON String] to type '[MyClass]'. Path 'responses[0].body', line 1, position 641."
  • 查看我的编辑。我修好了

标签: c# json json.net


【解决方案1】:

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

  1. 您可以使用 LINQ to JSON 预处理您的 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. 您可以为与每个 Body 对象对应的 POCO 引入通用 JsonConverter,该对象将传入的嵌入 JSON 字符串文字解析为 LINQ to JSON 层次结构,然后对其进行反序列化:

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

    请注意,转换器会检查传入的 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 Respons
{
    public string info { get; set; }
    public Body body { get; set; }
}

public class RootObject
{
    public List<Respons> responses { get; set; }
}

【讨论】:

    【解决方案2】:

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

    var token = JToken.Parse(text);
    var json = JObject.Parse((string) token);
    

    【讨论】:

      【解决方案3】:
      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);
      }
      

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

      【讨论】:

      • @downvoter 我的回答不正确吗?像 OP 这样已经可以使用 JSON.Net 的人是否无法使用我的答案?
      • 看起来我们都被否决了。我希望我能找到一种方法来清理 JSON,而无需为所有不同的 API 响应创建重复的类(这是一个更复杂的简化示例),我可以使用通用的东西来解析值。
      【解决方案4】:

      这是我根据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);
      

      【讨论】:

        猜你喜欢
        • 2018-05-03
        • 2018-01-06
        • 1970-01-01
        • 2018-06-09
        • 1970-01-01
        • 2019-08-27
        • 2020-05-27
        相关资源
        最近更新 更多