I've been trying to figure this issue out, can someone tell me what I'm doing wrong and how I can fix the following error when attempting to consume data from an external API?
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 GetAllCityData()
{
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 parseData = JsonSerializer.Deserialize<City>(result.Content);
var readData = result.Content.ReadAsAsync<IList<City>>();
readData.Wait();
city = readData.Result;
}
else
{
city = Enumerable.Empty<City>();
ModelStateDictionary modelState = new ModelStateDictionary();
modelState.AddModelError(string.Empty, "Server error has occured, please contact admin for help");
}
}
return (ActionResult)city;
City.cs
public class City
{
public string country { get; set; }
public string city { get; set; }
public int count { get; set; }
public int locations { get; set; }
public DateTime firstUpdated { get; set; }
public DateTime lastUpdated { get; set; }
public string[] parameters { get; set; }
}
CodePudding user response:
You are trying to deserialize a json string before you get it.
it is much better to create async API , but if it is impossible, try this code (assumming that API returns a City collection, if not - change to an expected class)
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("https://docs.openaq.org/");
var contentType = new MediaTypeWithQualityHeaderValue("application/json");
client.DefaultRequestHeaders.Accept.Add(contentType);
var response = client.GetAsync("v2/cities").Result;
if (response.IsSuccessStatusCode)
{
var json = response.Content.ReadAsStringAsync().Result;
var result= JsonSerializer.Deserialize<List<City>>(json);
return Ok(result);
}
}
CodePudding user response:
From the comments, it looks like the real problem is how to skip the meta
attribute of the JSON document and read results
instead.
The string contains an object, not an array:
{"meta":{"name":"openaq-api","license":"CC BY 4.0d","website":"https://u50g7n0cbj.execute-api.us-east-1.amazonaws.com/","page":1,"limit":100,"found":3153},
"results":[{"country":"US","city":"007","count":22608,"locations":6,"firstUpdated":"2018-08-09T09:00:00 00:00","lastUpdated":"2022-05-09T14:00:00 00:00","parameters":["pm25"]},
{"country":"US","city":"019","count":2112,"locations":1,"firstUpdated":"2020-09-18T16:00:00 00:00","lastUpdated":"2020-10-23T17:00:00 00:00","parameters":["pm25"]},
...
You can't skip the meta
attribute by trying to deserialize a List<City>
. Serialization libraries will try to math the input to their target type and throw if these don't match.
It's possible to handle meta
as overflow data. This is explained in How to handle overflow JSON or use JsonElement or JsonNode in System.Text.Json.
class Payload
{
[JsonExtensionData]
public Dictionary<string, JsonElement>? Meta { get; set; }
public City[] Results {get;set;}
}
...
var results=await client.GetFromJsonAsync<Payload>("https://docs.openaq.org/v2/cities");
Using this technique allows inspecting the metadata if necessary. The code uses .NET 6's GetFromJsonAsync method because ... I'm lazy.