使用 System.Text.Json 修改 JSON 文件

Lou*_*les 15 c# json asp.net-core asp.net-core-3.0 system.text.json

我知道您可以使用 Newtonsoft 轻松完成此操作。然而,当我使用 .NET Core 3.0 时,我正在尝试使用新方法与 JSON 文件交互System.Text.Json——即——我拒绝相信我正在尝试做的事情有那么困难!

我的应用程序需要列出尚未添加到我的数据库中的用户。为了获取所有用户的完整列表,该应用程序从 Web API 检索 JSON 字符串。我现在需要遍历这些用户中的每一个,并在将新的 JSON 列表返回到我的视图之前检查他们是否已经添加到我的应用程序中,以便它可以向最终用户显示新的潜在用户。

由于我最终会在流程结束时返回另一个 JSON,因此我不想特别费心将其反序列化为模型。请注意,来自 API 的数据结构可能会改变,但它总会有一个键,我可以从中与我的数据库记录进行比较。

我的代码目前看起来像这样:

using (WebClient wc = new WebClient())
{
    var rawJsonDownload = wc.DownloadString("WEB API CALL");
    var users =  JsonSerializer.Deserialize<List<UserObject>>(rawJsonDownload);

    foreach (var user in users.ToList())
    {
        //Check if User is new
        if (CHECKS)
        {
            users.Remove(user);
        }
    }

    return Json(users); 
}
Run Code Online (Sandbox Code Playgroud)

为了实现对 Newtonsoft 来说相当微不足道的东西,这似乎有很多障碍需要跳过。

任何人都可以建议我这样做的更好方法 - 理想情况下,不需要UserObject?

dbc*_*dbc 34

您的问题是您希望检索、过滤和传递一些 JSON,而无需为该 JSON 定义完整的数据模型。借助 Json.NET,您可以为此目的使用LINQ to JSON。您的问题是,目前可以使用 轻松解决这个问题System.Text.Json吗?

从 .NET Core 3.0 开始,这不能很容易地完成,System.Text.Json因为:

  1. JsonDocument,对应于JToken或的类型XDocument是只读的。它只能用于检查JSON 值,不能用于修改或创建 JSON 值。

    目前有一个未解决的问题Writable Json DOM #39922跟踪此问题。

  2. System.Text.Json不支持JSONPath,这在此类应用程序中通常非常方便。

    目前有一个未解决的问题Add JsonPath support to JsonDocument/JsonElement #41537跟踪这个。

话虽如此,假设您有以下 JSON:

[
  {
    "id": 1,
    "name": "name 1",
    "address": {
      "Line1": "line 1",
      "Line2": "line 2"
    },
    // More properties omitted
  }
  //, Other array entries omitted
]
Run Code Online (Sandbox Code Playgroud)

以及一些Predicate<long> shouldSkip过滤器方法,指示是否id不应返回具有特定条目的条目,对应CHECKS于您的问题。你有哪些选择?

您可以使用JsonDocument并返回一些过滤后的JsonElement节点集。如果过滤逻辑非常简单并且您不需要以任何其他方式修改 JSON,那么这是有道理的。请注意,根据文档,这JsonDocument是一次性的,实际上必须进行处置以最大程度地减少垃圾收集器 (GC) 在高使用场景中的影响。因此,为了返回 a您必须克隆它。JsonElement

以下代码显示了一个示例:

using var usersDocument = JsonDocument.Parse(rawJsonDownload);
var users = usersDocument.RootElement.EnumerateArray()
    .Where(e => !shouldSkip(e.GetProperty("id").GetInt64()))
    .Select(e => e.Clone())
    .ToList();

return Json(users);
Run Code Online (Sandbox Code Playgroud)

样机小提琴 #1在这里

您可以创建一个部分数据模型,该模型仅反序列化过滤所需的属性,并将剩余的 JSON 绑定到一个[JsonExtensionDataAttribute]属性。 这应该允许您实现必要的过滤,而无需对整个数据模型进行硬编码。

为此,请定义以下模型:

public class UserObject
{
    [JsonPropertyName("id")]
    public long Id { get; set; }

    [System.Text.Json.Serialization.JsonExtensionDataAttribute]
    public IDictionary<string, object> ExtensionData { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

并反序列化和过滤如下:

var users = JsonSerializer.Deserialize<List<UserObject>>(rawJsonDownload);
users.RemoveAll(u => shouldSkip(u.Id));

return Json(users);
Run Code Online (Sandbox Code Playgroud)

这种方法确保可以适当地反序列化与过滤相关的属性,而无需对 JSON 的其余部分做出任何假设。虽然这不像使用 LINQ to JSON 那样容易,但总代码复杂性受过滤检查的复杂性限制,而不是 JSON 的复杂性。事实上,我的观点是,在实践中,这种方法比纯JsonDocument方法更容易使用,因为如果以后需要,它可以更容易地向 JSON 注入修改。

样机小提琴 #2在这里

没有你选择事情,你可能会考虑抛弃WebClientHttpClient,并使用async反序列化。例如:

var httpClient = new HttpClient();
using var usersDocument = await JsonDocument.ParseAsync(await httpClient.GetStreamAsync("WEB API CALL"));
Run Code Online (Sandbox Code Playgroud)

或者

var users = await JsonSerializer.DeserializeAsync<List<UserObject>>(await httpClient.GetStreamAsync("WEB API CALL"));
Run Code Online (Sandbox Code Playgroud)

您也需要您的 API 方法转换async为。

  • 哇,这是一个非常深入且解释清楚的答案:)谢谢! (3认同)