简体   繁体   中英

How to deserialize complex JSON with Newtonsoft?

I'm new at the forum and I have an issue.

I'm trying to deserialize the Neo Feed of the NASA API with Newtonsoft and I'm getting this error

Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current JSON object (eg {"name":"value"}) into type 'System.Collections.Generic.IEnumerable 1[NasaApi.Models.Near_Earth_Objects]' because the type requires a JSON array (eg [1,2,3]) to deserialize correctly. To fix this error either change the JSON to a JSON array (eg [1,2,3]) or change the deserialized type so that it is a normal .NET type (eg 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 'links', line 1, position 9. at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent) at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType) at Newtonsoft.Json.JsonSerializer.Deserialize(JsonReader reader, Type objectType) at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings) at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings) at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value) at NasaApi.Services.NearEarthObjectService.GetAllNeos() in C:\Users\santanitaxx1050\Desktop\NasaApi\NasaApi\Services\NearEarthObjectService.cs:line 18 at lambda_method5(Closure , Object ) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask 1[NasaApi.Models.Near_Earth_Objects]' because the type requires a JSON array (eg [1,2,3]) to deserialize correctly. To fix this error either change the JSON to a JSON array (eg [1,2,3]) or change the deserialized type so that it is a normal .NET type (eg 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 'links', line 1, position 9. at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent) at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType) at Newtonsoft.Json.JsonSerializer.Deserialize(JsonReader reader, Type objectType) at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings) at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings) at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value) at NasaApi.Services.NearEarthObjectService.GetAllNeos() in C:\Users\santanitaxx1050\Desktop\NasaApi\NasaApi\Services\NearEarthObjectService.cs:line 18 at lambda_method5(Closure , Object ) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask 1[NasaApi.Models.Near_Earth_Objects]' because the type requires a JSON array (eg [1,2,3]) to deserialize correctly. To fix this error either change the JSON to a JSON array (eg [1,2,3]) or change the deserialized type so that it is a normal .NET type (eg 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 'links', line 1, position 9. at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent) at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType) at Newtonsoft.Json.JsonSerializer.Deserialize(JsonReader reader, Type objectType) at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings) at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings) at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value) at NasaApi.Services.NearEarthObjectService.GetAllNeos() in C:\Users\santanitaxx1050\Desktop\NasaApi\NasaApi\Services\NearEarthObjectService.cs:line 18 at lambda_method5(Closure , Object ) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask 1 actionResultValueTask) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecuted ContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext) at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

HEADERS

Accept: / Host: localhost:7008 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.79 Safari/537.36 :method: GET Accept-Encoding: gzip, deflate, br Accept-Language: es-ES,es;q=0.9 Cache-Control: no-cache postman-token: ec30b624-b8b6-770d-57ce-4b6dcda1ffc2 sec-gpc: 1 sec-fetch-site: none sec-fetch-mode: cors sec-fetch-dest: empty

I tried

public async Task<IEnumerable<Near_Earth_Objects>> GetAllNeos()
{
    var json = await _httpClient.GetStringAsync($"feed?start_date=2021-11-07&end_date=2021-11-10&api_key=Na1sKwJGK1HVeOF4Yx8aLNp4u8ygT5GSSMF26HQ2");
    return JsonConvert.DeserializeObject<IEnumerable<Near_Earth_Objects>>(json);
}

With this JSON

{
"links": {
    "next": "http://www.neowsapp.com/rest/v1/feed?start_date=2021-12-12&end_date=2021-12-15&detailed=false&api_key=Na1sKwJGK1HVeOF4Yx8aLNp4u8ygT5GSSMF26HQ2",
    "prev": "http://www.neowsapp.com/rest/v1/feed?start_date=2021-12-06&end_date=2021-12-09&detailed=false&api_key=Na1sKwJGK1HVeOF4Yx8aLNp4u8ygT5GSSMF26HQ2",
    "self": "http://www.neowsapp.com/rest/v1/feed?start_date=2021-12-09&end_date=2021-12-12&detailed=false&api_key=Na1sKwJGK1HVeOF4Yx8aLNp4u8ygT5GSSMF26HQ2"
},
"element_count": 76,
"near_earth_objects": {
    "2021-12-12": [
        {
            "links": {
                "self": "http://www.neowsapp.com/rest/v1/neo/2004341?api_key=Na1sKwJGK1HVeOF4Yx8aLNp4u8ygT5GSSMF26HQ2"
            },
            "id": "2004341",
            "neo_reference_id": "2004341",
            "name": "4341 Poseidon (1987 KF)",
            "nasa_jpl_url": "http://ssd.jpl.nasa.gov/sbdb.cgi?sstr=2004341",
            "absolute_magnitude_h": 16.05,
            "estimated_diameter": {
                "kilometers": {
                    "estimated_diameter_min": 1.6389095149,
                    "estimated_diameter_max": 3.6647130844
                },
                "meters": {
                    "estimated_diameter_min": 1638.9095149478,
                    "estimated_diameter_max": 3664.7130843945
                },
                "miles": {
                    "estimated_diameter_min": 1.0183708442,
                    "estimated_diameter_max": 2.277146434
                },
                "feet": {
                    "estimated_diameter_min": 5376.9998930214,
                    "estimated_diameter_max": 12023.337275805
                }
            },
            "is_potentially_hazardous_asteroid": false,
            "close_approach_data": [
                {
                    "close_approach_date": "2021-12-12",
                    "close_approach_date_full": "2021-Dec-12 13:35",
                    "epoch_date_close_approach": 1639316100000,
                    "relative_velocity": {
                        "kilometers_per_second": "17.8282207618",
                        "kilometers_per_hour": "64181.5947426121",
                        "miles_per_hour": "39879.9470221525"
                    },
                    "miss_distance": {
                        "astronomical": "0.3316696597",
                        "lunar": "129.0194976233",
                        "kilometers": "49617074.634744839",
                        "miles": "30830620.5431592182"
                    },
                    "orbiting_body": "Earth"
                }
            ],
            "is_sentry_object": false
        },

making this model

public class Near_Earth_Objects
{
    [JsonProperty("id")]
    public int Id { get; set; }
    [JsonProperty("name")]
    public string Nombre { get; set; }
    
    [JsonProperty("estimated_diameter:kilometers:estimated_diameter_min")]
    public double DiametroMin { get; set; }
    
    [JsonProperty("estimated_diameter:kilometers:estimated_diameter_max")]
    public double DiametroMax { get; set; }
    
    [JsonProperty("close_approach_data:relative_velocity:kilometers_per_hour")]
    public double Velocidad { get; set; }
    
    [JsonProperty("close_approach_data: close_approach_date")]
    public DateTime Fecha { get; set; }
    
    [JsonProperty("close_approach_date: orbiting_body")]
    public string Planeta { get; set; }
}

my deserialization code is this

public async Task<IEnumerable<Near_Earth_Objects>> GetAllNeos()
{
    var json = await _httpClient.GetStringAsync($"feed?start_date=2021-11-07&end_date=2021-11-10&api_key=Na1sKwJGK1HVeOF4Yx8aLNp4u8ygT5GSSMF26HQ2");
    return JsonConvert.DeserializeObject<IEnumerable<Near_Earth_Objects>>(json);
}

to see what JSON response try this on POSTMAN:

https://api.nasa.gov/neo/rest/v1/feed?start_date=2021-11-07&end_date=2021-11-10&api_key=Na1sKwJGK1HVeOF4Yx8aLNp4u8ygT5GSSMF26HQ2

Thanks to all!! Sorry my bad english, I'm from Spain :)

This is indeed a complex schema so it's not easy to generate DTOs automatically. For that reason, you shouldn't use custom names for the properties. It makes it even harder to find problems.

  1. The near_earth_objects section is actually a dictionary of daily observations. Instead of creating a near_earth_objects class it's better to use a Dictionary<string,Observation[]> .
  2. links contains links to the current, next and previous pages in the feed. This means you can actually create one class and reuse it both at the root level and in daily observations

You can use a DTO generator tool to get started but the result will need modifications. Tools won't be able to recognize that near_earth_objects is a dictionary and can easily end up creating new types for every entry.

The DTOs

Using your JSON sample I created initial classes using Visual Studio's Paste as Json and then modified them to work properly.

public class Rootobject
{
    public PageLinks links { get; set; }
    public int element_count { get; set; }
    public Dictionary<string,Observation[]> near_earth_objects { get; set; }
}

public class PageLinks
{
    public string? next { get; set; }
    public string? prev { get; set; }
    public string self { get; set; }
}

The Observation class uses the same PageLinks class for the links property:

public class Observation
{
    public PageLinks links { get; set; }
    public string id { get; set; }
    public string neo_reference_id { get; set; }
    public string name { get; set; }
    public string nasa_jpl_url { get; set; }
    public float absolute_magnitude_h { get; set; }
    public Estimated_Diameter estimated_diameter { get; set; }
    public bool is_potentially_hazardous_asteroid { get; set; }
    public Close_Approach_Data[] close_approach_data { get; set; }
    public bool is_sentry_object { get; set; }
}

The rest of the classes require no modification:

public class Estimated_Diameter
{
    public Kilometers kilometers { get; set; }
    public Meters meters { get; set; }
    public Miles miles { get; set; }
    public Feet feet { get; set; }
}

public class Kilometers
{
    public float estimated_diameter_min { get; set; }
    public float estimated_diameter_max { get; set; }
}

public class Meters
{
    public float estimated_diameter_min { get; set; }
    public float estimated_diameter_max { get; set; }
}

public class Miles
{
    public float estimated_diameter_min { get; set; }
    public float estimated_diameter_max { get; set; }
}

public class Feet
{
    public float estimated_diameter_min { get; set; }
    public float estimated_diameter_max { get; set; }
}

public class Close_Approach_Data
{
    public string close_approach_date { get; set; }
    public string close_approach_date_full { get; set; }
    public long epoch_date_close_approach { get; set; }
    public Relative_Velocity relative_velocity { get; set; }
    public Miss_Distance miss_distance { get; set; }
    public string orbiting_body { get; set; }
}

public class Relative_Velocity
{
    public string kilometers_per_second { get; set; }
    public string kilometers_per_hour { get; set; }
    public string miles_per_hour { get; set; }
}

public class Miss_Distance
{
    public string astronomical { get; set; }
    public string lunar { get; set; }
    public string kilometers { get; set; }
    public string miles { get; set; }
}

Testing the model

With this model, the following test passes:

[Fact]
public async Task GetFeed()
{
    var client = new HttpClient();
    var url = "http://www.neowsapp.com/rest/v1/feed?start_date=2021-12-09&end_date=2021-12-12&detailed=false&api_key=Na1sKwJGK1HVeOF4Yx8aLNp4u8ygT5GSSMF26HQ2";
    var feed = await client.GetFromJsonAsync<Rootobject>(url);

    Assert.Equal(76,feed.element_count);
    var allObservations = feed.near_earth_objects
        .SelectMany(p => p.Value)
        .ToList();
    Assert.Equal(76,allObservations.Count);
}

The Error is because of the below line:

JsonConvert.DeserializeObject<IEnumerable<Near_Earth_Objects>>(json);

You received this error because API returns a Single Object but you want to Deserialize it to an IEnumerable.

For resolving change it to this:

JsonConvert.DeserializeObject<ApiResultDataModel>(json);

and ApiResultDataModel is like below:

public class ApiResultDataModel
{
   public Links links { get; set; }
   public int element_count { get; set; }
   public Dictionary<string, Near_Earth_Objects[]> near_earth_objects { get; set; }
}

public class Links
{
    public string next { get; set; }
    public string prev { get; set; }
    public string self { get; set; }
}

by doing this your data will be deserialized successfully:

在此处输入图像描述 I hope this answer helps you. Goodluck.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM