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

Please avoid a use of .Wait() and .Result and try using async / await according to the best practices as described below:

Avoid blocking calls
https://learn.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?view=aspnetcore-6.0

Although I do not know the use of .Wait() and .Result is causing the issue please do it right first.

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;  
                city = Enumerable.Empty<City>();     

The result like this:

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:

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

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

Thank you for that! this is exactly what i was trying to do at an earlier stage but wasn't sure about how to go around doing it. Despite me not receiving the same error i am now receiving the following exception.

Below is the updated code also:

public async Task<ActionResult> GetAllCityData()
            IEnumerable<City> city = null;
            using (var client = new HttpClient())
                client.BaseAddress = new Uri("https://docs.openaq.org/");
                var responseTask = client.GetAsync($"v2/cities");
                await responseTask;
                //JsonConvert.SerializeObject(responseTask);
                var result = responseTask.Result;
                if (result.IsSuccessStatusCode)
                    var readData = await result.Content.ReadAsAsync<OpenAQResponse<IList<City>>>();
                    city = readData.results;    
                    city = Enumerable.Empty<City>();
                    ModelStateDictionary modelState = new ModelStateDictionary();
                    modelState.AddModelError(string.Empty, "Server error has occured, please contact admin for help");
            return (ActionResult)city;
												

An unhandled exception occurred while processing the request.
JsonReaderException: Unexpected character encountered while parsing value: {. Path 'meta', line 1, position 9.
Newtonsoft.Json.JsonTextReader.ReadStringValue(ReadType readType)