使用 System.Text.Json 修改 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); 
}

这似乎有很多箍要跳过,以实现Newtonsoft相当微不足道的事情。

谁能建议我更好的方法来做到这一点——理想情况下,不需要UserObject

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

从 .NET 6 开始,这不能用 System.Text.Json 那么容易完成,因为它不支持 JSONPath,而 JSONPath 在此类应用程序中通常非常方便。 目前有一个未解决的问题 将 JsonPath 支持添加到 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过滤方法,指示是否不应返回具有特定id的条目,对应于您问题中的CHECKS。 你有什么选择?

在 .NET 6 及更高版本中,您可以将 JSON 解析为JsonNode,编辑其内容,并返回修改后的 JSON。JsonNode表示可编辑的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 及更高版本中,可以解析为JsonDocument并返回一组筛选的JsonElement节点。 如果筛选逻辑非常简单,并且不需要以任何其他方式修改 JSON,则此方法非常有效。 但请注意JsonDocument的以下限制:

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

  • 根据文档,JsonDocument是一次性的,实际上必须进行处理,以最大程度地减少垃圾回收器 (GC( 在高使用率方案中的影响。 要返回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在这里。

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

相关内容

  • 没有找到相关文章