1

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 (e.g. {"name":"value"}) into type 'System.Collections.Generic.IEnumerable1[NasaApi.Models.Near_Earth_Objects]' 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 '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, ValueTask1 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(ActionExecutedContextSealed 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 :)

5
  • Are you deserializing directly to a Near_Earth_Object? What's your code to deserialize the JSON? Commented Apr 6, 2022 at 8:00
  • There is no need in these modern times to hand craft json classes. Json to C# is built right into visual studio (copy the json to the clipboard, Edit/Paste Special/Paste JSON as Classes), or use one of the numerous online converters (which allow better conversion, similar to the JsonProperty you mention in the question); Commented Apr 6, 2022 at 8:00
  • 2
    You need to deserialize the root object which contains near_earth_objects, incidentally you should declare near_earth_objects like this Dictionary <DateTime, Near_Earth_Objects[]> near_earth_objects Commented Apr 6, 2022 at 8:16
  • @Neil actually, there is. That tool is a very crude tool that could easily generate 10 identical classes instead of reusing the same one. This is an unusual case too, as near_earth_objects is used as a dictionary. If you actually tried Paste As JSON you'd see the result uses one property per date instead of a dictionary and multiple identical classes instead of reusing eg links. Without a JSON Schema or OpenAPI spec, tools can only make guesses Commented Apr 6, 2022 at 8:17
  • @PanagiotisKanavos I sort of agree, but it will give a basic working version that can be tweaked manually. The OP problem appears to be missing some of the root items, which the tool would create. Commented Apr 6, 2022 at 8:34

2 Answers 2

0

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);
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks!!! this works for me.The Paste JSON as classes tool did not work, I'll look after DTO Generation tool but I'm using Visual Studio 2022 and they are not compatible.
0

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:

enter image description here I hope this answer helps you. Goodluck.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.