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

Posted

技术标签:

【中文标题】使用 System.Text.Json 修改 JSON 文件【英文标题】:Modifying a JSON file using System.Text.Json 【发布时间】:2020-03-18 17:32:50 【问题描述】:

我知道您可以使用 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); 

这似乎是一个很多的障碍,以实现对 Newtonsoft 来说相当微不足道的事情。

谁能告诉我一个更好的方法——最好是不需要UserObject

【问题讨论】:

"...我相信 Newtonsoft 的一些事情是相当微不足道的" - 如果您将其作为学习练习,为什么不先创建 Newtonsoft 解决方案,然后再反向 -在此基础上设计您的 .Net 解决方案。据我所知,上面的代码对于这类事情来说是微不足道的——你对数据库的检查依赖于源中的一个值,无论你使用什么框架,删除一个节点都需要一个代码步骤使用。 这可能不是你的意思,但你可以使用 List 上的 RemoveAll 方法来减少你的代码。这会将您的解决方案减少到 3 行代码(反序列化、删除、序列化)。它不会比这更短。如果这不是您想要的,也许您可​​以使用 newtonsoft 提供一个示例实现,说明您尝试使用 system.text.json 完成的工作 由于您使用的是System.Text.Json,您可能会考虑将WebClient 换成HttpClient 并进行异步反序列化。参见例如stu.dev/a-look-at-jsondocument 【参考方案1】:

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

从 .NET 6 开始,使用 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
]

还有一些Predicate&lt;long&gt; shouldSkip 过滤方法指示是否不应返回具有特定id 的条目,对应于您问题中的CHECKS。你有什么选择?

在 .NET 6 及更高版本中,您可以将 JSON 解析为 JsonNode,编辑其内容,然后返回修改后的 JSON。JsonNode 表示 editable JSON 文档对象模型因此最接近于 Newtonsoft 的 JToken 层次结构。

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

var root = JsonNode.Parse(rawJsonDownload).AsArray(); // AsArray() throws if the root node is not an array.
for (int i = root.Count - 1; i >= 0; i--)

    if (shouldSkip(root[i].AsObject()["id"].GetValue<long>()))
        root.RemoveAt(i);


return Json(root);

样机小提琴 #1 here

在 .NET Core 3.x 及更高版本中,您可以解析为 JsonDocument 并返回一些过滤后的 JsonElement 节点集。如果过滤逻辑非常简单并且您不需要以任何其他方式修改 JSON,则此方法效果很好。但请注意JsonDocument 的以下限制:

JsonDocumentJsonElement 是只读的。它们只能用于检查 JSON 值,不能修改或创建 JSON 值。

JsonDocument 是一次性的,实际上必须被丢弃以在高使用情况下最大限度地减少垃圾收集器 (GC) 的影响,根据 docs。要返回 JsonElement,您必须 clone 它。

问题中的过滤场景很简单,可以使用以下代码:

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

样机小提琴 #2 here.

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

为此,请定义以下模型:

public class UserObject

    [JsonPropertyName("id")]
    public long Id  get; set; 
    
    [System.Text.Json.Serialization.JsonExtensionDataAttribute]
    public IDictionary<string, object> ExtensionData  get; set; 

并反序列化过滤如下:

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

return Json(users);

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

样机小提琴 #3 here.

无论您选择哪种方式,您都可以考虑将WebClient 换成HttpClient 并使用async 反序列化。例如:

var httpClient = new HttpClient(); // Cache statically and reuse in production
var root = await httpClient.GetFromJsonAsync<JsonArray>("WEB API CALL");

或者

using var usersDocument = await JsonDocument.ParseAsync(await httpClient.GetStreamAsync("WEB API CALL"));

或者

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

您还需要将您的 API 方法 convert 设为 async

【讨论】:

哇,一个非常深入且解释清楚的答案:) 谢谢! 一直是最好的答案之一 这是最好的答案。我在指定位置插入属性时遇到问题,所以想知道您是否可以通过以下question 提供帮助 更新:现在有support for JSON Path。 从 .Net 6 documentation987654339@开始可用

以上是关于使用 System.Text.Json 修改 JSON 文件的主要内容,如果未能解决你的问题,请参考以下文章

使用 System.Text.Json 从 json 文件读取到 IEnumerable

如何使用 System.Text.Json 忽略错误值

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

(de) 使用 System.Text.Json 序列化流

使用 System.Text.Json 转换不一致的 json 值 [重复]

使用 .NET core 3.0/System.text.Json 解析 JSON 文件