JSON Error when attempting to consume data from an external API.

Aaron soggi 246 Reputation points
2022-05-09T23:40:45.777+00:00

Api : https://docs.openaq.org/

Api path which I'm trying to extract data from : https://docs.openaq.org/v2/cities

The aim of this Task : The client needs to support the retrieval of data for a city

This is the error im getting below.

System.AggregateException
HResult=0x80131500
Message=One or more errors occurred. (Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[OpenAQAirQuality.Models.City]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
Path 'meta', line 1, position 8.)
Source=System.Private.CoreLib
StackTrace:
at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
at System.Threading.Tasks.Task.Wait()
at OpenAQAirQuality.ClientConnectionHandler.ClientConnectionHandler.GetCityData() in C:\Users\Aaron\source\repos\OpenAQAirQuality\OpenAQAirQuality\Services\ClientConnectionHandler.cs:line 29
at OpenAQAirQuality.Controllers.airQualityController.getCity() in C:\Users\Aaron\source\repos\OpenAQAirQuality\OpenAQAirQuality\Controllers\airQualityController.cs:line 19
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()

This exception was originally thrown at this call stack:
[External Code]

Inner Exception 1:
JsonSerializationException: Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[OpenAQAirQuality.Models.City]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
Path 'meta', line 1, position 8.

ClientConnectionHandler.cs code:

 public ActionResult GetCityData()
        {
            IEnumerable<City> city = null;
            using (var client = new HttpClient())
            {
                client.BaseAddress = new Uri("https://docs.openaq.org/");
                var responseTask = client.GetAsync("v2/cities");
                responseTask.Wait();

                var result = responseTask.Result;
                if (result.IsSuccessStatusCode)
                {

                    var readData = result.Content.ReadAsAsync<IList<City>>();
                    readData.Wait(); // this is where the error is occuring
                    city = readData.Result;

                }
                else
                {
                    city = Enumerable.Empty<City>();
                    ModelStateDictionary modelState = new ModelStateDictionary();
                    modelState.AddModelError(string.Empty, "Server error has occured");


                }
            }

            return (ActionResult)city;

City.cs model class

public class City
    {
        [JsonProperty]
        public string country { get; set; }
        [JsonProperty]
        public string city { get; set; }
        [JsonProperty]
        public int count { get; set; }
        [JsonProperty]
        public int locations { get; set; }
        [JsonProperty]
        public DateTime firstUpdated { get; set; }
        [JsonProperty]
        public DateTime lastUpdated { get; set; }
        [JsonProperty]
        public string[] parameters { get; set; }

    }

airQualityController.cs code:

public class airQualityController : Controller
    {
        private readonly IClientConnectionHandler _iclientConnectionHandler;
        public airQualityController(IClientConnectionHandler clientConnectionHandler)
        {
            _iclientConnectionHandler = clientConnectionHandler;
        }

        public async Task<ActionResult> GetCity()
        {
            var result = await _iclientConnectionHandler.GetAllCityData();
            return View(result);       
        }

    }

The problem I'm having is figuring out how to skip the "meta" attribute of the JSON document and read results instead.

Can anyone give me a hand with this? I've been trying to figure it out for two days now and i cant find the answer. I've posted this question on other forums and no one has been really clear about what i need to do.

I Would really appreciate it if someone could tell me what code is needed so the data is consumed successfully.

Thankyou!!!

ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,611 questions
{count} votes

Accepted answer
  1. Zhi Lv - MSFT 32,446 Reputation points Microsoft Vendor
    2022-05-10T08:34:31.43+00:00

    Hi @Aaron soggi ,

    Based on the API response data, you could try to create the following models:

    public class City  
    {  
        [JsonProperty]  
        public string country { get; set; }  
        [JsonProperty]  
        public string city { get; set; }  
        [JsonProperty]  
        public int count { get; set; }  
        [JsonProperty]  
        public int locations { get; set; }  
        [JsonProperty]  
        public DateTime firstUpdated { get; set; }  
        [JsonProperty]  
        public DateTime lastUpdated { get; set; }  
        [JsonProperty]  
        public string[] parameters { get; set; }  
    
    }  
    
    public class Meta  
    {  
        public string name { get; set; }  
        public string license { get; set; }  
        public string website { get; set; }  
        public int page { get; set; }  
        public int limit { get; set; }  
        public int found { get; set; }  
    }   
    
    public class RootModel  
    {  
        public Meta meta { get; set; }  
        public List<City> results { get; set; }  
    }  
    

    Then, use the following code to convert the response data and get the City:

            IEnumerable<City> city = null;  
            using (var client = new HttpClient())  
            {  
                client.BaseAddress = new Uri("https://docs.openaq.org/");  
                var responseTask = client.GetAsync("v2/cities");  
                responseTask.Wait();  
    
                var result = responseTask.Result;  
                if (result.IsSuccessStatusCode)  
                {  
    
                    var readData = result.Content.ReadAsStringAsync();  
                    readData.Wait();   
    
                    //using System.Text.Json;  
                    var jsonresult = JsonSerializer.Deserialize<RootModel>(readData.Result);  
    
                    city = jsonresult.results;  
    
                }  
                else  
                {  
                    city = Enumerable.Empty<City>();     
                }  
            }  
    

    The result like this:

    200505-image.png

    Update:

    We can also use the following code:

        public async Task<IActionResult> IndexAsync(string city)  
        {  
            var client = new HttpClient();  
            client.BaseAddress = new Uri("https://docs.openaq.org/");  
            var results = await client.GetFromJsonAsync<RootModel>($"v2/cities").ConfigureAwait(false);  
            return View();  
        }  
    

    The result like this:

    200739-image.png


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    Best regards,
    Dillion

    1 person found this answer helpful.

1 additional answer

Sort by: Most helpful
  1. Leonardo Zacche 81 Reputation points
    2022-05-10T01:57:57.513+00:00

    The content of the json returned by the service does not maps directly to your IList<City>. The list you are interest is the property "results" of a greater object which represents the entire response.

    You can define this greater object as an "envelop" (or wrapper) to you model (IList<City>). Something like that:

    public class OpenAQResponse<T> 
    {
       [JsonProperty]
       public string meta { get; set; }
    
       [JsonProperty]
       public T results { get; set; }
    }
    

    Then change line 14 of GetCityData to

    var readData = result.Content.ReadAsAsync<OpenAQResponse<IList<City>>>();
    

    Your list of cities will be in readData.results, and it will be an IList<City>. It seems this wrapper will be usefull also when getting other data (othen than cities).

    In the future you can get more info from the service just refactoring the meta property to better provide info about the data (eg: page number, limit and qty of cities found).

    1 person found this answer helpful.

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.