0

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?

1
  • I have seen like a dozen questions about this in the past week. Why is everyone writing or consuming APIs that return this format? Commented Oct 10, 2021 at 19:26

3 Answers 3

3

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
Sign up to request clarification or add additional context in comments.

Comments

1

"data" property in your original json is double serialized, so it should be deserialized the second time from json string to object

so try this code

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

fixed 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"
}

Test

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

classes

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

Comments

0

the Data in apiResponse is not an object, it's a string , you can convert the string into a model using Newtonsoft Json.Net , as is explained in this issue : Deserializing stringified (quote enclosed) nested objects with Newtonsoft Json.Net

2 Comments

Application is using currently only .NET5 JsonSerializer. Is it reasonable to use two serializers in same application or can this solved using .NET own Deserializer ?
@Andrus It is doable using a temporary object in between or using a JsonConverter which I've outlined below - you can use 2 serializer but in this case, it would be overkill

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.