我知道您可以使用 Newtonsoft 轻松做到这一点。然而,当我使用 .NET Core 3.0 时,我正在尝试使用新方法与 JSON 文件交互——即 System.Text.Json
——我拒绝相信我是什么尝试去做就是那么困难!
我的应用程序需要列出尚未添加到我的数据库中的用户。为了获得所有用户的完整列表,应用程序从 Web API 检索 JSON 字符串。我现在需要循环浏览每个用户并检查他们是否已添加到我的应用程序,然后再将新的 JSON 列表返回到我的 View ,以便它可以向最终用户显示新的潜在用户。
因为我最终会在流程结束时返回另一个 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这在此类应用程序中通常非常方便。当前有一个 Unresolved 问题 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
过滤器方法指示条目是否具有特定 id
不应返回,对应CHECKS
在你的问题中。你有什么选择?
在 .NET 6 及更高版本中,您可以将 JSON 解析为 JsonNode
,编辑其内容,并返回修改后的JSON。 A 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);
样机 fiddle #1 here
在 .NET Core 3.x 及更高版本中,您可以解析为 JsonDocument
并返回一些过滤后的 JsonElement
集节点。如果过滤逻辑非常简单并且您不需要以任何其他方式修改 JSON,则此方法效果很好。但请注意 JsonDocument
的以下限制:
JsonDocument
和JsonElement
是只读的。它们只能用于检查 JSON 值,不能用于修改或创建 JSON 值。JsonDocument
根据 docs,是一次性的,实际上必须进行处理以在高使用率场景中最大限度地减少垃圾收集器 (GC) 的影响 .为了返回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);
样机 fiddle #2 here .
在任何版本中,您都可以创建一个部分数据模型,该模型仅反序列化过滤所需的属性,其余 JSON 绑定(bind)到 [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 注入(inject)修改。
样机 fiddle #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"));
您需要 convert你的 API 方法是 async
关于c# - 使用 System.Text.Json 修改 JSON 文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58997718/