Home > Mobile >  Deserialize a complex JSON response without property names
Deserialize a complex JSON response without property names

Time:11-23

I want to call an API from my ASP.Net Core app, but the response is not in a great format:

[
  {
    page: 1,
    pages: 1,
    per_page: 50,
    total: 2,
    sourceid: "2",
    sourcename: "World Development Indicators",
    lastupdated: "2021-10-28"
  },
  [
    {
      indicator: {
        id: "NY.GDP.PCAP.KD.ZG",
        value: "GDP per capita growth (annual %)"
      },
      country: {
        id: "CA",
        value: "Canada"
      },
      countryiso3code: "CAN",
      date: "2020",
      value: -6.33904652535297,
      unit: "",
      obs_status: "",
      decimal: 1
    },
    {
      indicator: {
        id: "NY.GDP.PCAP.KD.ZG",
        value: "GDP per capita growth (annual %)"
      },
      country: {
        id: "CA",
        value: "Canada"
      },
      countryiso3code: "CAN",
      date: "2019",
      value: 0.43021113414872,
      unit: "",
      obs_status: "",
      decimal: 1
    }
  ]
]

Here is the API url: https://api.worldbank.org/v2/country/can/indicator/NY.GDP.PCAP.KD.ZG?format=json&mrv=2

I have typed these classes for the response:

public class WorldBankApiResponseGlobal
  {
    public int Page { get; set; }
    public int Pages { get; set; }
    public int Per_Page { get; set; }
    public int Total { get; set; }
    public string Sourceid { get; set; }
    public string Sourcename { get; set; }
  }

  public class WorldBankApiIdValue
  {
    public string Id { get; set; }
    public string Value { get; set; }
  }

  public class WorldBankApiResponse
  {
    public WorldBankApiIdValue Indicator { get; set; }
    public WorldBankApiIdValue Country { get; set; }
    public int Value { get; set; }
    public string Date { get; set; }
    public string Countryiso3code { get; set; }
  }

But, since the response doesn't have property values for the objects (ie: [{},[]] instead of ["data": {}, "indicators": []], I don't know how to properly deserialize the API call response...

Normally, I would do it as such:

var response = await _client.GetFromJsonAsync<List<WorldBankApiResponseGlobal>>(worldBankApiUrl);

But does anyone know how I would do it with this response type?

CodePudding user response:

I don't know if you can do it with GetFromJsonAsync but using Newtonsoft.Json. You can craft a special convert that will check if the token is an array or an object. And deserialize to the appoprriate type.

class WorldBankApiResponseUnionConverter : JsonConverter
{
    public override bool CanConvert(Type t) => t == typeof(WorldBankApiResponseUnion) || t == typeof(WorldBankApiResponseUnion?);

    public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
    {
        switch (reader.TokenType)
        {
            case JsonToken.StartObject:
                var objectValue = serializer.Deserialize<WorldBankApiResponseGlobal>(reader);
                return new WorldBankApiResponseUnion { WorldBankApiResponseGlobal = objectValue };
            case JsonToken.StartArray:
                var arrayValue = serializer.Deserialize<WorldBankApiResponse[]>(reader);
                return new WorldBankApiResponseUnion { WorldBankApiResponse = arrayValue };
        }
        throw new Exception("Cannot unmarshal type WorldBankApiResponseUnion");
    }

Live demo : https://dotnetfiddle.net/3Rqnya
Using the automated tool : https://app.quicktype.io/?l=csharp

CodePudding user response:

Edit: That comment above posting https://app.quicktype.io/?l=csharp to generate the solution looks like it would do the trick although it generates a ton of code. If there was an easy way to remove the header, the below would be a better choice.

I'm not sure how to remove the "header", but maybe you can exclude the "header" to leave the core data and then you would be left with something that could be List

public class WorldBankApiResponse
{
  public WorldBankApiIdValue Indicator { get; set; }
  public WorldBankApiIdValue Country { get; set; }
  public int Value { get; set; }
  public string Date { get; set; }
  public string Countryiso3code { get; set; }
}

CodePudding user response:

You can use JsonDocument from System.Text.Json to deserialize the first and second items in the top-level array into separate objects, without using GetFromJsonAsync.

var json = @"
[
  { ""page_count"": 10 },
  [
    { ""id"": 1 },
    { ""id"": 2 }
  ]
]
";

class Header
{
    public int page_count { get; set; }
}
class Item
{
    public int id { get; set; }
}

using var document = JsonDocument.Parse(json);
var header = document.RootElement[0].Deserialize<Header>();
var body = document.RootElement[1].Deserialize<List<Item>>();

If you're using Newtonsoft.Json, the equivalent is JArray (and JToken in general):

var jarray = JArray.Parse(json);
var header = jarray[0].ToObject<Header>();
var body = jarray[1].ToObject<List<Item>>();

CodePudding user response:

Try this. It was tested in Visual Studio

var parsedArray=JArray.Parse(json);

// this is just an array part
List<WorldBankApiResponse> worldBankApiResponses =  
JsonConvert.DeserializeObject<List<WorldBankApiResponse>>(parsedArray[1].ToString());

// this is just a header part
var wb = 
JsonConvert.DeserializeObject<WorldBankApiResponseGlobal>(parsedArray[0].ToString());

//this is the whole object
 var worldBankApiResponseGlobal= new WorldBankApiResponseGlobal{
     Page=wb.Page,
     Pages=wb.Pages,
     PerPage=wb.PerPage,
     Total=wb.Total,
     Sourceid=wb.Sourceid,
     Sourcename=wb.Sourcename,
     Lastupdated=wb.Lastupdated,
     WorldBankApiResponses=  worldBankApiResponses
 };

classes

public partial class WorldBankApiResponseGlobal
    {
        [JsonProperty("page")]
        public long Page { get; set; }

        [JsonProperty("pages")]
        public long Pages { get; set; }

        [JsonProperty("per_page")]
        public long PerPage { get; set; }

        [JsonProperty("total")]
        public long Total { get; set; }

        [JsonProperty("sourceid")]
        public long Sourceid { get; set; }

        [JsonProperty("sourcename")]
        public string Sourcename { get; set; }

        [JsonProperty("lastupdated")]
        public DateTimeOffset Lastupdated { get; set; }

        [JsonProperty("WorldBankApiResponses")]
        public List<WorldBankApiResponse> WorldBankApiResponses { get; set; }
    }

    public partial class WorldBankApiResponse
    {
        [JsonProperty("indicator")]
        public WorldBankApiIdValue Indicator { get; set; }

        [JsonProperty("country")]
        public WorldBankApiIdValue Country { get; set; }

        [JsonProperty("countryiso3code")]
        public string Countryiso3Code { get; set; }

        [JsonProperty("date")]
        public long Date { get; set; }

        [JsonProperty("value")]
        public double Value { get; set; }

        [JsonProperty("unit")]
        public string Unit { get; set; }

        [JsonProperty("obs_status")]
        public string ObsStatus { get; set; }

        [JsonProperty("decimal")]
        public long Decimal { get; set; }
    }

    public partial class WorldBankApiIdValue
    {
        [JsonProperty("id")]
        public string Id { get; set; }

        [JsonProperty("value")]
        public string Value { get; set; }
    }
  • Related