如何反序列化作为 System.Text.Json 中的字符串的嵌套 JSON 对象?

Posted

技术标签:

【中文标题】如何反序列化作为 System.Text.Json 中的字符串的嵌套 JSON 对象?【英文标题】:How do I deserialize a nested JSON object which is a string in System.Text.Json? 【发布时间】:2021-11-29 00:30:19 【问题描述】:

我在 ASP.NET 5 MVC 应用程序中反序列化 JSON,使用:

var tulemus = JsonSerializer.Deserialize<EstoJarelMaksTulemnus>(apiResponse, new JsonSerializerOptions  PropertyNameCaseInsensitive = true );

public class EstoJarelMaksTulemnus

   public string[] Errors  get; set; 
   public EstoData Data  get; set; 
   public string Mac  get; set; 


public class EstoData

   public string Id  get; set; 
   public string Status  get; set; 
   public string Purchase_url  get; set; 
   public string Merchant_reference  get; set; 
   public decimal Amount  get; set; 
   public string Currency  get; set; 
   public bool Is_test  get; set; 
   public string Return_url  get; set; 
   public string Notification_url  get; set; 

这会引发错误:

System.Text.Json.JsonException:无法转换 JSON 值 到 Store.Controllers.CheckoutController+EstoData。路径:$.data | 行号:0 | BytePositionInLine: 497. 在 System.Text.Json.ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(类型 属性类型)...

apiResponse 是一个单行字符串,包含一个嵌入的EstoData

"errors":[],"data":"\"id\":\"iUW3YiDIz5Ahg5eO8hV7d3Cv7SVbZ913\",\"status\":\"CREATED\",\"purchase_url\":\"https:\\\/\\\/user.esto.com\\\/application\\\/iUW3YiDIeO8hV7d3Cv7SVbZ913\",\"merchant_reference\":\"15502\",\"amount\":93.95,\"currency\":\"EUR\",\"is_test\":true,\"return_url\":\"http:\\\/\\\/localhost:54274\\\/CheckoutController\\\/EstoJarelmaksOK?tellimus=104742\",\"notification_url\":\"http:\\\/\\\/localhost:54274\\\/CheckoutController\\\/EstoJarelmaksTeade?tellimus=104742\"","mac":"E9C3E61FC347D8043ABDCF464D537C37A609EC878F95B5F526271A2287F2D2E507B5A14FA3AF5F7A6D4CDECB6E8A1DBDF9A5633E0B3AD96DA35FA1C9"

位置 497 似乎指向 Data 属性的末尾,位于 mac 之前。

考虑到 EstoData 在我的对象中是具体的嵌套类型,但在 JSON 中是 string,我如何将此 JSON 反序列化为 EstoJarelMaksTulemnus

【问题讨论】:

在过去的一周里,我已经看到了十几个关于此的问题。为什么每个人都在编写或使用返回这种格式的 API? 【参考方案1】:

您需要创建一个自定义JsonConverter&lt;T&gt;,在这种情况下TEstoData,以便能够正确反序列化嵌套的Data JSON 对象。

这应该可以再次用于反序列化和序列化对象:

StringToEstoDataConverter.cs

public class StringToEstoDataConverter : JsonConverter<EstoData>

  public override EstoData? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
  
      using (var jsonDoc = JsonDocument.ParseValue(ref reader))
      
          var infoData = jsonDoc.RootElement.GetString();
          if (infoData != null)
              return JsonSerializer.Deserialize<EstoData>(infoData, options);
      

      return default;
  

  public override void Write(Utf8JsonWriter writer, EstoData value, JsonSerializerOptions options)
  
      JsonSerializer.Serialize(writer, value, value.GetType(), options);
  

EstoJarelMaksTulemnus.cs

public class EstoJarelMaksTulemnus

  public string[] Errors  get; set; 

  [JsonConverter(typeof(StringToEstoDataConverter))]
  public EstoData Data  get; set; 

  public string Mac  get; set; 

用法:

var tulemus = JsonSerializer.Deserialize<EstoJarelMaksTulemnus>(apiResponse, new JsonSerializerOptions  PropertyNameCaseInsensitive = true );

这是一个工作演示:

public class Program

  public static void Main()
  
      var data =
          "\"errors\":[],\"data\":\"\\\"id\\\":\\\"iUW3YiDIz5Ahg5eO8hV7d3Cv7SVbZ913\\\",\\\"status\\\":\\\"CREATED\\\",\\\"purchase_url\\\":\\\"https:\\\\\\/\\\\\\/user.esto.ee\\\\\\/application\\\\\\/iUW3YiDIz5Ahg5eO8hV7d3Cv7SVbZ913\\\",\\\"merchant_reference\\\":\\\"158502\\\",\\\"amount\\\":93.95,\\\"currency\\\":\\\"EUR\\\",\\\"is_test\\\":true,\\\"return_url\\\":\\\"http:\\\\\\/\\\\\\/localhost:54274\\\\\\/CheckoutController\\\\\\/EstoJarelmaksOK?tellimus=104742\\\",\\\"notification_url\\\":\\\"http:\\\\\\/\\\\\\/localhost:54274\\\\\\/CheckoutController\\\\\\/EstoJarelmaksTeade?tellimus=104742\\\"\",\"mac\":\"E9C3E61FC347D80200F542C43ABDCF464D537C37A609EC878F95B5F526271A2287F2D2E507B5A14FA3AF5F7A6D4CDECB6E8A1DBDF9A5633E0B3AD96DA35FA1C9\"";


      var tulemus = JsonSerializer.Deserialize<EstoJarelMaksTulemnus>(data, new JsonSerializerOptions  PropertyNameCaseInsensitive = true );

      Console.WriteLine(tulemus.Errors.Length);
      Console.WriteLine(tulemus.Data.Id);
      Console.WriteLine(tulemus.Data.Status);
      Console.WriteLine(tulemus.Data.Purchase_url);
      Console.WriteLine(tulemus.Data.Merchant_reference);
      Console.WriteLine(tulemus.Data.Amount);
      Console.WriteLine(tulemus.Data.Currency);
      Console.WriteLine(tulemus.Data.Is_test);
      Console.WriteLine(tulemus.Data.Return_url);
      Console.WriteLine(tulemus.Data.Notification_url);
      Console.WriteLine(tulemus.Mac);
  


public class StringToEstoDataConverter : JsonConverter<EstoData>

  public override EstoData? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
  
      using (var jsonDoc = JsonDocument.ParseValue(ref reader))
      
          var infoData = jsonDoc.RootElement.GetString();
          if (infoData != null)
              return JsonSerializer.Deserialize<EstoData>(infoData, options);
      

      return default;
  

  public override void Write(Utf8JsonWriter writer, EstoData value, JsonSerializerOptions options)
  
      JsonSerializer.Serialize(writer, value, value.GetType(), options);
  



public class EstoJarelMaksTulemnus

  public string[] Errors  get; set; 

  [JsonConverter(typeof(StringToEstoDataConverter))]
  public EstoData Data  get; set; 

  public string Mac  get; set; 


public class EstoData

  public string Id  get; set; 
  public string Status  get; set; 
  public string Purchase_url  get; set; 
  public string Merchant_reference  get; set; 
  public decimal Amount  get; set; 
  public string Currency  get; set; 
  public bool Is_test  get; set; 
  public string Return_url  get; set; 
  public string Notification_url  get; set; 

输出:

0
iUW3YiDIz5Ahg5eO8hV7d3Cv7SVbZ913
CREATED
https://user.esto.ee/application/iUW3YiDIz5Ahg5eO8hV7d3Cv7SVbZ913
158502
93.95
EUR
True
http://localhost:54274/CheckoutController/EstoJarelmaksOK?tellimus=104742
http://localhost:54274/CheckoutController/EstoJarelmaksTeade?tellimus=104742
E9C3E61FC347D80200F542C43ABDCF464D537C37A609EC878F95B5F526271A2287F2D2E507B5A14FA3AF5F7A6D4CDECB6E8A1DBDF9A5633E0B3AD96DA35FA1C9

【讨论】:

【参考方案2】:

你的原始json中的“data”属性是双重序列化的,所以它应该第二次从json字符串反序列化为对象

所以试试这段代码

var json=...your json;

var jd = JsonSerializer.Deserialize<Root>(json, new JsonSerializerOptions  PropertyNameCaseInsensitive = true );

// the second deserialize data property from json string to instance
var jdd= JsonSerializer.Deserialize<Data>(jd.data);

JsonDeserialized result = new JsonDeserialized errors=jd.errors, data=jdd, mac=jd.mac;

var jsonFixed=JsonSerializer.Serialize(result);

固定的json


  "errors": [],
  "data": 
    "id": "iUW3YiDIz5Ahg5eO8hV7d3Cv7SVbZ913",
    "status": "CREATED",
    "purchase_url": "https://user.esto.ee/application/iUW3YiDIz5Ahg5eO8hV7d3Cv7SVbZ913",
    "merchant_reference": "158502",
    "amount": 93.95,
    "currency": "EUR",
    "is_test": true,
    "return_url": "http://localhost:54274/CheckoutController/EstoJarelmaksOK?tellimus=104742",
    "notification_url": "http://localhost:54274/CheckoutController/EstoJarelmaksTeade?tellimus=104742"
  ,
  "mac": "E9C3E61FC347D80200F542C43ABDCF464D537C37A609EC878F95B5F526271A2287F2D2E507B5A14FA3AF5F7A6D4CDECB6E8A1DBDF9A5633E0B3AD96DA35FA1C9"

测试

JsonDeserialized deserializedFixedJson = Deserialize<JsonDeserialized>(jsonFixed, new JsonSerializerOptions  PropertyNameCaseInsensitive = true );

public class JsonDeserialized

    public List<object> errors  get; set; 
    public Data data  get; set; 
    public string mac  get; set; 

public class Data

    public string id  get; set; 
    public string status  get; set; 
    public string purchase_url  get; set; 
    public string merchant_reference  get; set; 
    public double amount  get; set; 
    public string currency  get; set; 
    public bool is_test  get; set; 
    public string return_url  get; set; 
    public string notification_url  get; set; 

public class Root

    public List<object> errors  get; set; 
    public string data  get; set; 
    public string mac  get; set; 

【讨论】:

【参考方案3】:

apiResponse 中的Data 不是对象,它是一个字符串,您可以使用Newtonsoft Json.Net 将字符串转换为模型,如本期所述:Deserializing stringified (quote enclosed) nested objects with Newtonsoft Json.Net

【讨论】:

应用程序当前仅使用 .NET5 JsonSerializer。在同一个应用程序中使用两个序列化器是否合理,或者可以使用 .NET 自己的 Deserializer 解决吗? @Andrus 在两者之间使用临时对象或使用我在下面概述的 JsonConverter 是可行的 - 您可以使用 2 个序列化程序,但在这种情况下,这将是矫枉过正

以上是关于如何反序列化作为 System.Text.Json 中的字符串的嵌套 JSON 对象?的主要内容,如果未能解决你的问题,请参考以下文章

使用 System.Text.Json 反序列化匿名类型

如何在反序列化之前使用 System.Text.Json 验证 JSON

如何让 System.Text.Json 将对象反序列化为其原始类型?

如何使用 System.Text.Json 序列化/反序列化非枚举类型的嵌套字典?

将 false 反序列化为 null (System.Text.Json)

如何使用 System.Text.Json API 将流反序列化为对象