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

25

我知道你可以轻松地使用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?


1
如果您正在进行这项学习练习,为什么不先创建一个Newtonsoft解决方案,然后再根据它反向工程化您的.Net解决方案,我相信这应该是相当简单的事情。就我所知,对于这种类型的事情,上面的代码几乎是微不足道的 - 您对数据库的检查依赖于源中的值,并且无论使用什么框架,删除节点都需要进行代码步骤。 - melkisadek
这可能不是你的重点,但你可以使用List上的RemoveAll方法来减少代码量。这将把你的解决方案减少到3行代码(反序列化,删除,序列化)。没有比这更短的了。如果这不是你想要的,也许你可以给出一个使用Newtonsoft实现的示例,说明你正在尝试使用System.Text.Json完成什么任务。 - PaulVrugt
由于您正在使用“System.Text.Json”,因此您可以考虑放弃“WebClient”并改用“HttpClient”进行异步反序列化。请参见例如 https://stu.dev/a-look-at-jsondocument/。 - dbc
1个回答

55
您的问题是想要检索、过滤和传递某些JSON,而无需为该JSON定义完整的数据模型。使用Json.NET,您可以使用LINQ to JSON 来实现此目的。您的问题是,是否可以使用 System.Text.Json 来轻松解决这个问题? 截至.NET 6,由于没有JSONPath支持,使用System.Text.Json不能像以前那样轻松地完成这个任务。在此方面通常非常便利。目前有一个未解决的问题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
]

在.NET 6及更高版本中,您可以将JSON解析为JsonNode,编辑其内容并返回修改后的JSON。JsonNode表示可编辑的JSON文档对象模型,因此最接近于Newtonsoft的JToken层次结构。
以下代码显示了一个示例:
而一些 Predicate<long> shouldSkip 过滤方法则指示不应返回具有特定 id 的条目,相当于您问题中的 CHECKS。您有哪些选项?
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);

Mockup fiddle #1 在这里

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

这里是第二个模型样例 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注入修改变得更加容易。
模拟fiddle #3 在这里无论您选择哪种方法,都可以考虑放弃WebClient,改用HttpClient和使用异步反序列化。例如:
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方法转换async


3
哇,非常深入和详细地解释了答案 :) 谢谢! - Louis Miles
更新:现在已经支持 JSON Path - gregsdennis
自 .Net 6 起已经提供了文档 - elena
非常感谢您提供的精彩答案 :) - elena
@elena - 答案已更新。 - dbc
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接