首页 > 解决方案 > Modifying a JSON file using System.Text.Json

问题描述

I know you can do this easily with Newtonsoft. As I am working with .NET Core 3.0, however, I am trying to use the new methods for interacting with JSON files —i.e., System.Text.Json—and I refuse to believe that what I am trying to do is all that difficult!

My application needs to list users that have not already been added to my database. In order to get the full list of all users, the app retrieves a JSON string from a web API. I now need to cycle through each of these users and check if they have already been added to my application before returning a new JSON list to my view so that it can display the new potential users to the end user.

As I am ultimately returning another JSON at the end of the process, I don't especially want to bother deserializing it to a model. Note that the structure of data from the API could change, but it will always have a key from which I can compare to my database records.

My code currently looks like this:

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

This seems like a lot of hoops to jump through in order to achieve something that would be fairly trivial with Newtonsoft.

Can anyone advise me on a better way of doing this—and, ideally, without the need for the UserObject?

标签: c#jsonasp.net-coreasp.net-core-3.0system.text.json

解决方案


您的问题是您想检索、过滤和传递一些 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<long> shouldSkip具有特定内容的条目,对应于您的问题。你有什么选择?idCHECKS

在 .NET 6 及更高版本中,您可以将 JSON 解析为 a JsonNode,编辑其内容并返回修改后的 JSON。 AJsonNode表示可编辑的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在这里

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

  • JsonDocument并且JsonElement是只读的。它们只能用于检查JSON 值,不能用于修改或创建 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);

样机小提琴#2在这里

在任何版本中,您都可以创建一个部分数据模型,该模型仅反序列化您过滤所需的属性,并将剩余的 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在这里

无论您选择哪一个,您都可以考虑放弃WebClientHttpClient使用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 方法转换async为。


推荐阅读