Search code examples
c#asp.net-coreasp.net-core-mvcjson-deserializationsystem.text.json

How do I deserialize a nested JSON object which is a string in System.Text.Json?


I'm deserializing JSON in a ASP.NET 5 MVC application using:

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

This throws the error:

System.Text.Json.JsonException: The JSON value could not be converted to Store.Controllers.CheckoutController+EstoData. Path: $.data | LineNumber: 0 | BytePositionInLine: 497. at System.Text.Json.ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type propertyType) ...

apiResponse is a single line string, containing an embedded 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"}

Position 497 seems to point to the end of the Data property before mac.

How can I deserialize this JSON to EstoJarelMaksTulemnus, considering EstoData is a concrete nested type in my object but a string in the JSON?


Solution

  • You need to create a custom JsonConverter<T>, where T is EstoData in this case, to be able to correctly deserialise the nested Data JSON object.

    This should work, both for deserialisation and serialisation of the object again:

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

    Usage:

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

    Here's a working demo:

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

    Output:

    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