简体   繁体   English

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

[英]Modifying a JSON file using System.Text.Json

I know you can do this easily with Newtonsoft.我知道您可以使用 Newtonsoft 轻松做到这一点。 As I am working with .NET Core 3.0, however, I am trying to use the new methods for interacting with JSON files —ie, System.Text.Json —and I refuse to believe that what I am trying to do is all that difficult!然而,当我正在使用 .NET Core 3.0 时,我正在尝试使用新方法与 JSON 文件进行交互——即System.Text.Json很难相信这一切!

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.为了获取所有用户的完整列表,应用程序从 web API 中检索 JSON 字符串。 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.我现在需要遍历这些用户中的每一个,并检查他们是否已经添加到我的应用程序中,然后再将新的 JSON 列表返回到我的视图,以便它可以向最终用户显示新的潜在用户。

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.由于我最终在该过程结束时返回另一个 JSON,我不想特别费心将其反序列化为 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.请注意,来自 API 的数据结构可能会发生变化,但它总是有一个键,我可以从中与我的数据库记录进行比较。

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.为了实现对 Newtonsoft 来说相当微不足道的事情,这似乎需要跳过很多障碍。

Can anyone advise me on a better way of doing this—and, ideally, without the need for the UserObject ?任何人都可以建议我以更好的方式执行此操作 - 理想情况下,不需要UserObject吗?

Your problem is that you would like to retrieve, filter, and pass along some JSON without needing to define a complete data model for that JSON.您的问题是您想检索、过滤和传递一些 JSON,而无需为该 JSON 定义完整的数据 model。 With Json.NET, you could use LINQ to JSON for this purpose.对于 Json.NET,您可以使用LINQ 到 JSON来实现此目的。 Your question is, can this currently be solved as easily with System.Text.Json ?您的问题是,目前可以使用System.Text.Json轻松解决这个问题吗?

As of .NET Core 3.0, this cannot be done quite as easily with System.Text.Json because:从 .NET Core 3.0 开始,这不能使用System.Text.Json轻松完成,因为:

  1. JsonDocument , the type corresponding to JToken or XDocument , is read-only. JsonDocument ,对应于JTokenXDocument的类型,是只读的。 It can be used only to examine JSON values, not to modify or create JSON values.它只能用于检查JSON 值,不能修改或创建 JSON 值。

    There is currently an open issue Writable Json DOM #39922 tracking this.目前有一个未解决的问题Writable Json DOM #39922跟踪此问题。

  2. System.Text.Json has no support for JSONPath which is often quite convenient in such applications. System.Text.Json不支持JSONPath ,这在此类应用程序中通常非常方便。

    There is currently an open issue Add JsonPath support to JsonDocument/JsonElement #41537 tracking this.当前有一个未解决的问题Add JsonPath support to JsonDocument/JsonElement #41537跟踪此问题。

That being said, imagine you have the following JSON:话虽如此,假设您有以下 JSON:

[
  {
    "id": 1,
    "name": "name 1",
    "address": {
      "Line1": "line 1",
      "Line2": "line 2"
    },
    // More properties omitted
  }
  //, Other array entries omitted
]

And some Predicate<long> shouldSkip filter method indicating whether an entry with a specific id should not be returned, corresponding to CHECKS in your question.还有一些Predicate<long> shouldSkip过滤器方法指示是否不应返回具有特定id的条目,对应于您问题中的CHECKS What are your options?你有什么选择?

You could use JsonDocument and return some filtered set of JsonElement nodes .您可以使用JsonDocument并返回一些过滤的JsonElement节点集 This makes sense if the filtering logic is very simple and you don't need to modify the JSON in any other way.如果过滤逻辑非常简单并且您不需要以任何其他方式修改 JSON,则这是有道理的。 Be aware that JsonDocument is disposable, and in fact must needs be disposed to minimize the impact of the garbage collector (GC) in high-usage scenarios , according to the docs .请注意,根据文档JsonDocument是一次性的,实际上必须进行处理以最大程度地减少垃圾收集器 (GC) 在高使用情况下的影响。 Thus, in order to return a JsonElement you must clone it.因此,为了返回JsonElement ,您必须克隆它。

The following code shows an example of this:以下代码显示了一个示例:

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

Mockup fiddle #1 here .样机小提琴#1在这里

You could create a partial data model that deserializes only the properties you need for filtering, with the remaining JSON bound to a [JsonExtensionDataAttribute] property.您可以创建部分数据 model 仅反序列化过滤所需的属性,剩余的 JSON 绑定到[JsonExtensionDataAttribute]属性。 This should allow you to implement the necessary filtering without needing to hardcode an entire data model.这应该允许您实现必要的过滤,而无需对整个数据 model 进行硬编码。

To do this, define the following model:为此,请定义以下 model:

public class UserObject
{
    [JsonPropertyName("id")]
    public long Id { get; set; }

    [System.Text.Json.Serialization.JsonExtensionDataAttribute]
    public IDictionary<string, object> ExtensionData { get; set; }
}

And deserialize and filter as follows:并反序列化和过滤如下:

var users = JsonSerializer.Deserialize<List<UserObject>>(rawJsonDownload);
users.RemoveAll(u => shouldSkip(u.Id));

return Json(users);

This approach ensures that properties relevant to filtering can be deserialized appropriately without needing to make any assumptions about the remainder of the JSON.这种方法确保可以适当地反序列化与过滤相关的属性,而无需对 JSON 的其余部分做出任何假设。 While this isn't as quite easy as using LINQ to JSON, the total code complexity is bounded by the complexity of the filtering checks, not the complexity of the JSON.虽然这并不像使用 LINQ 到 JSON 那样简单,但总代码复杂度受限于过滤检查的复杂性,而不是 Z0ECD11C1D7A287401D148A23BBD7A2F 的复杂性。 And in fact my opinion is that this approach is, in practice, a little easier to work with than the pure JsonDocument approach because it makes it somewhat easier to inject modifications to the JSON if required later.事实上,我的观点是,在实践中,这种方法比纯JsonDocument方法更容易使用,因为如果以后需要,它可以更容易地向 JSON 注入修改。

Mockup fiddle #2 here .样机小提琴#2在这里

No matter which you choose , you might consider ditching WebClient for HttpClient and using async deserialization.无论您选择哪一个,您都可以考虑放弃WebClient来使用HttpClient并使用async反序列化。 Eg:例如:

var httpClient = new HttpClient();
using var usersDocument = await JsonDocument.ParseAsync(await httpClient.GetStreamAsync("WEB API CALL"));

Or或者

var users = await JsonSerializer.DeserializeAsync<List<UserObject>>(await httpClient.GetStreamAsync("WEB API CALL"));

You would need to convert your API method to be async as well.您还需要API 方法转换为async方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM