【问题标题】:How to deserialize complex JSON with Newtonsoft?如何使用 Newtonsoft 反序列化复杂的 JSON?
【发布时间】:2022-07-08 14:46:18
【问题描述】:

我是论坛的新手,我有一个问题。

我正在尝试使用 Newtonsoft 反序列化 NASA API 的 Neo Feed,但出现此错误

Newtonsoft.Json.JsonSerializationException:无法将当前 JSON 对象(例如 {"name":"value"})反序列化为类型 '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) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|10_0(ControllerActionInvoker 调用者,任务 lastTask,下一个状态,作用域范围,对象状态,布尔 isCompleted) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed 上下文) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(状态&下一个,范围&范围,对象&状态,布尔& isCompleted) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|13_0(ControllerActionInvoker 调用程序,任务 lastTask,下一个状态,作用域范围,对象状态,布尔 isCompleted) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|20_0(ResourceInvoker 调用程序,任务 lastTask,下一个状态,作用域范围,对象状态,布尔 isCompleted) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker 调用程序,任务任务,IDisposable 范围) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker 调用程序,任务任务,IDisposable 范围) 在 Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(端点端点、任务 requestTask、ILogger 记录器) 在 Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext 上下文) 在 Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext) 在 Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext,ISwaggerProvider swaggerProvider) 在 Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext 上下文)

标题

接受:/ 主机:本地主机:7008 用户代理:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.79 Safari/537.36 :方法:获取 接受编码:gzip、deflate、br 接受语言:es-ES,es;q=0.9 缓存控制:无缓存 邮递员令牌:ec30b624-b8b6-770d-57ce-4b6dcda1ffc2 秒-gpc:1 sec-fetch-site: 无 秒取模式:cors sec-fetch-dest: 空

我试过了

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);
}

有了这个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
        },

制作这个模型

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

我的反序列化代码是这样的

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);
}

查看 JSON 响应在 POSTMAN 上试试这个:

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

谢谢大家!! 对不起,我的英语不好,我来自西班牙:)

【问题讨论】:

  • 您是否直接反序列化为Near_Earth_Object?反序列化 JSON 的代码是什么?
  • 现代时代没有必要手工制作 json 类。 Json to C# 内置于 Visual Studio(将 json 复制到剪贴板,编辑/粘贴特殊/粘贴 JSON 作为类),或使用众多在线转换器之一(允许更好的转换,类似于您在问题);
  • 您需要反序列化 包含 near_earth_objects 的根对象,顺便说一下,您应该像这样声明near_earth_objects Dictionary &lt;DateTime, Near_Earth_Objects[]&gt; near_earth_objects
  • @Neil 实际上,有。该工具是一个非常粗糙的工具,可以轻松生成 10 个相同的类,而不是重复使用同一个类。这也是一个不寻常的情况,因为near_earth_objects 被用作字典。如果您确实尝试过Paste As JSON,您会看到结果每个日期使用一个属性而不是字典和多个相同的类,而不是重用例如链接。没有 JSON Schema 或 OpenAPI 规范,工具只能猜测
  • @PanagiotisKanavos 我有点同意,但它将提供一个可以手动调整的基本工作版本。 OP 问题似乎缺少该工具将创建的一些根项。

标签: c# json .net serialization json.net


【解决方案1】:

这确实是一个复杂的架构,因此自动生成 DTO 并不容易。因此,您不应该为属性使用自定义名称。这使得发现问题变得更加困难。

  1. near_earth_objects 部分实际上是一本日常观察字典。与其创建near_earth_objects 类,不如使用Dictionary&lt;string,Observation[]&gt;
  2. links 包含指向提要中当前、下一页和上一页的链接。这意味着您实际上可以创建一个类并在根级别和日常观察中重用它

您可以使用 DTO 生成器工具开始,但结果需要修改。工具将无法识别 near_earth_objects 是一个字典,并且很容易最终为每个条目创建新类型。

DTO

使用您的 JSON 示例,我使用 Visual Studio 的 Paste as Json 创建了初始类,然后对其进行了修改以使其正常工作。

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

Observation 类对links 属性使用相同的PageLinks 类:

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

其余类无需修改:

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

测试模型

使用此模型,以下测试通过:

[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);
}

【讨论】:

  • 谢谢!!!这对我有用。粘贴 JSON 作为类工具不起作用,我会照顾 DTO 生成工具,但我使用的是 Visual Studio 2022,它们不兼容。
【解决方案2】:

错误是由于以下行:

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

您收到此错误是因为 API 返回一个单个对象,但您想将其反序列化为 IEnumerable。

为了解决改变它:

JsonConvert.DeserializeObject<ApiResultDataModel>(json);

ApiResultDataModel 如下:

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

通过这样做,您的数据将被成功反序列化:

我希望这个答案对你有所帮助。 祝你好运。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-06
    • 1970-01-01
    相关资源
    最近更新 更多