System.Text.Json 反序列化来自 API 调用的嵌套对象 - 数据包装在父 JSON 属性中

Posted

技术标签:

【中文标题】System.Text.Json 反序列化来自 API 调用的嵌套对象 - 数据包装在父 JSON 属性中【英文标题】:System.Text.Json Deserialize nested object from API call - Data is wrapped in parent JSON property 【发布时间】:2020-04-21 10:21:48 【问题描述】:

我有一个 API JSON 响应,它将数据内容包装在 data 属性中,如下所示:

 
   "data": 
      "email":"admin@example.com",
      "mobile":"+1555555123",
      "id":4,
      "first_name":"Merchant",
      "last_name":"Vendor",
      "role":"Merchant",
   

因此,当使用 RequestSharp 之类的库请求用户对象时,response.Content 的用户内容包含在 data json 属性中,因为它来自 API。代码:

var request = RequestHelper.CreateTokenRequest(email, password); // Create the request from a helper
var client = new RestClient(BaseUrl); // create new RestSharp Client
IRestResponse response = client.Execute(request); // execute the request
var content = response.Content; // raw content as string

这很好,但是当我用System.Text.Json 将json 反序列化为一个对象时,如下所示,将创建User 对象但不会分配任何属性,尽管这是预期的,因为序列化程序正在寻找具有first_namelast_name 的属性...而不是['data']['first_name']

User account = JsonSerializer.Deserialize<User>(response.Content, options);

如何让JsonSerializer.Deserialize 忽略data 包装器?在其他 API 调用中,它可能是 Object 的名称,例如 transactionuser,无论哪种方式,它都会包装数据。

其他说明:

我的目标是最新的 .Net Core 3.1,并且正在从 Newtonsoft Json.Net 迁移


我的用户对象:

using System.ComponentModel;
using System.Text.Json.Serialization;

namespace MyApplication.Models

    public interface IUser
    
        string FirstName  get; set; 
        string LastName  get; set; 
        string Email  get; set; 
        string Mobile  get; set; 
        string Id  get; set; 
        string Role  get; set; 
    

    public class User : IUser
    
        [JsonPropertyName("first_name")]
        public string FirstName  get; set; 

        [JsonPropertyName("last_name")]
        public string LastName  get; set; 

        [JsonPropertyName("email")]
        public string Email  get; set; 

        [JsonPropertyName("mobile")]
        public string Mobile  get; set; 

        [JsonPropertyName("id")]
        public string Id  get; set; 

        [JsonPropertyName("role")]
        public string Role  get; set; 

        [JsonIgnore]
        public string Token  get; set; 
    



解决后更新:

我从下面的 u/Nikunj Kakadiya 中选择了答案,作为可行的方法,并且与我最终做的最相似。

我创建了一个基于通用模板的容器类来处理data,如下所示:

public class Container<T>

    [JsonPropertyName("data")]
    public T Data  get; set; 

然后我使用该容器类来包装从 API 调用返回的 json 内容,如下所示:

var options = new JsonSerializerOptions

    AllowTrailingCommas = true
;

Container<User> accountContainer = JsonSerializer.Deserialize<Container<User>>(response.Content, options);
User account = accountContainer.Data;

此外,正如 u/Pavel Anikhouski 所指出的,我对 User 类的序列化导致了一个错误,需要我为 id 字段创建一个自定义转换器。 API 将id 作为整数返回,尽管它是User 类中的字符串。这是我遇到的最初令人困惑的错误,但我很快就弄清楚了:ERROR: The JSON value could not be converted to System.String. Path: $.data.id | LineNumber: 0 | BytePositionInLine: 77.

这里是自定义转换器IntToStringConverter

public class IntToStringConverter : JsonConverter<string>

    public override string Read(
        ref Utf8JsonReader reader,
        Type typeToConvert,
        JsonSerializerOptions options) => reader.GetInt32().ToString();

    public override void Write(
        Utf8JsonWriter writer,
        string value,
        JsonSerializerOptions options) =>
        writer.WriteStringValue(value);

然后更改User 类以使用客户转换器:

...
    [JsonPropertyName("id")]
    [JsonConverter(typeof(IntToStringConverter))]
    public string Id  get; set; 
...

【问题讨论】:

出现错误无法将令牌类型“数字”的值作为字符串获取。对于id 属性 您还需要答案吗?下面有几个有效的答案。如果您真的不关心根属性名称,您总是可以反序列化为字典并获取第一个值,即JsonSerializer.Deserialize&lt;Dictionary&lt;string, User&gt;&gt;(response.Content, options).Values.SingleOrDefault(); 【参考方案1】:

你们需要另开一堂课。下面给出了

Public class UserData

    public User data  get; set; ; 

现在您可以使用名为 UserData 的新类反序列化数据,如下所示

UserData account = JsonSerializer.Deserialize<UserData>(response.Content, options);

【讨论】:

但 OP 提到 在其他 API 调用中可能是对象的名称,例如 transactionuser 虽然我最终创建了一个模板容器类,但它仍然有效。查看上面的解决方案【参考方案2】:

可以使用 System.Text.Json API 获取 User 对象,而无需从 JSON 示例中指定属性名称 data

 
   "data": 
      "email":"admin@example.com",
      "mobile":"+1555555123",
      "id":4,
      "first_name":"Merchant",
      "last_name":"Vendor",
      "role":"Merchant",
   

通过以下代码

var document = JsonDocument.Parse(json, new JsonDocumentOptions  AllowTrailingCommas = true );
var enumerator = document.RootElement.EnumerateObject();
if (enumerator.MoveNext())

    var userJson = enumerator.Current.Value.GetRawText();
    var user = JsonSerializer.Deserialize<User>(userJson,
        new JsonSerializerOptions AllowTrailingCommas = true);

在上面的示例中,JsonDocument 被加载,RootElement 被枚举以获取第一个嵌套对象作为文本反序列化为 User 实例。

根据您的问题,按名称获取属性(如document.RootElement.GetProperty("data");)更容易,但实际上名称可能不同。通过indexer 访问,例如document.RootElement[0] 也是不可能的,因为它仅在元素的ValueKindArray 而不是Object 时才有效,就像你的情况一样

我还将"id":4, 更改为"id":"4",,因为出现错误

无法将令牌类型“数字”的值作为字符串获取。

【讨论】:

【参考方案3】:

你可以像这样创建一个对象:

  public class Data
   
    public string email  get; set; 
    public string mobile  get; set; 
    public int id  get; set; 
    public string first_name  get; set; 
    public string last_name  get; set; 
    public string role  get; set; 
    

   public class RootObject
   
    public Data data  get; set; 
   

然后

var data = JsonSerializer.Deserialize<RootObject>(JsonData);

然后你可以像下面这样访问数据:

RootObject.Data.email ;
RootObject.Data.first_name

此外,任何时候您需要将 JSON 字符串转换为 C# POCO 类,您都可以使用这样的工具:http://json2csharp.com/

【讨论】:

【参考方案4】:

要么摆脱数据对象,要么编写自定义 json 转换器。我建议更改发送的数据或直接使用它,因为您尝试做的不是最佳实践。

【讨论】:

以上是关于System.Text.Json 反序列化来自 API 调用的嵌套对象 - 数据包装在父 JSON 属性中的主要内容,如果未能解决你的问题,请参考以下文章

System.Text.Json 中是不是可以进行多态反序列化?

使用 System.Text.Json 反序列化匿名类型

如何在反序列化之前使用 System.Text.Json 验证 JSON

为啥使用 System.Text.Json 的 JSON 反序列化这么慢?

使用 System.Text.Json 使用动态键查询或反序列化 json

System.Text.Json 反序列化 uint