我知道你可以用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
的以下限制:
JsonDocument
和JsonElement
是只读的。 它们只能用于检查 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
。