【问题标题】:Equivalent of JObject in System.Text.JsonSystem.Text.Json 中 JObject 的等价物
【发布时间】:2021-01-07 21:16:51
【问题描述】:

我有一个具有JObject 类型属性的 DTO 类。这个 DTO 类在多个服务之间通过 HTTP 发送/接收。使用 JObject 是因为 ExtractedData 没有预定义的属性

public class MyDTO
{
    public JObject ExtractedData {get;set;}
}

我正在将此项目转换为 .NET 5。什么相当于 .NET 5 中的 JObject?我试图避免 JsonDocument 因为(来自docs):

JsonDocument 将数据的内存视图构建到池中 缓冲。因此,与 Newtonsoft.Json 中的 JObject 或 JArray 不同, JsonDocument 类型实现了 IDisposable 并且需要在一个 使用块。

我打算使用JsonElement。这是最合适的选择还是有任何其他类型可用于将 JSON 作为对象保存?

【问题讨论】:

    标签: c# .net-5 system.text.json


    【解决方案1】:

    JObject 最接近的等价物确实是JsonElement,因此您可以按如下方式修改您的DTO:

    public class MyDTO
    {
        public JsonElement ExtractedData {get;set;}
    }
    

    无需担心处理任何文档,因为在内部,JsonSerializer 使用的 JsonElementConverter 返回一个非池化元素(通过在 .NET 5 中克隆该元素)。

    但是,对应关系并不准确,因此请记住以下几点:

    1. JsonElement 代表任何 JSON 值,因此最接近于 JToken 而不是 JObject。由于JsonElementstruct,因此没有对应于JSON 对象的子类。如果您想将 ExtractedData 限制为 JSON 对象,您需要在 setter 中进行检查:

      public class MyDTO
      {
          JsonElement extractedData;
      
          public JsonElement ExtractedData
          {
              get => extractedData;
              set
              {
                  if (value.ValueKind != JsonValueKind.Object
                      // && value.ValueKind != JsonValueKind.Null Uncomment if you want to allow null
                      )
                      throw new ArgumentException(string.Format("{0} is not a JSON object type", value.ValueKind));
                  extractedData = value;
              }
          }
      }
      
    2. 由于JsonElement是一个结构体,所以默认值不是null。那么,它是什么?原来default(JsonElement)ValueKind = JsonValueKind.Undefined

      没有值(与 Null 不同)。

      如果您尝试用JsonSerializer 序列化这样的默认JsonElement,将引发异常。 IE。如果你只是这样做

      var json = JsonSerializer.Serialize(new MyDTO());
      

      然后会抛出System.InvalidOperationException: Operation is not valid due to the current state of the object. 异常。

      您有几个选项可以避免这个问题:

      • 在 .NET 5 中,您可以像这样申请 [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]

        public class MyDTO
        {
            [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
            public JsonElement ExtractedData {get;set;}
        }
        

        这会导致序列化期间跳过未初始化的 ExtractedData 值。

      • 在 .NET Core 3.x 中 JsonIgnoreCondition 不存在,因此您可以改为将 ExtractedData 定义为可为空:

        public class MyDTO
        {
            public JsonElement? ExtractedData {get;set;}
        }
        

        或者您可以将其初始化为 null JsonElement,如下所示:

        public class MyDTO
        {
            public JsonElement ExtractedData {get;set;} = JsonExtensions.Null;
        }
        
        public static class JsonExtensions
        {
            static readonly JsonElement nullElement = CreateNull();
        
            public static JsonElement Null => nullElement;
        
            static JsonElement CreateNull()
            {
                using var doc = JsonDocument.Parse("null");
                return doc.RootElement.Clone();
            }
        }
        

        这两个选项都会导致ExtractedData 的未初始化值序列化为null

    3. 另请参阅相关问题:

    【讨论】:

      【解决方案2】:

      截至 2021 年 11 月,.NET 6 引入了 System.Text.Json.Nodes 命名空间,其中:

      提供用于处理内存中可写文档对象的类型 模型 (DOM) 用于随机访问结构化文件中的 JSON 元素 数据视图

      四种新类型是JsonArrayJsonObjectJsonNodeJsonValue。 与JObject 最接近的类型是JsonObject,它提供了类似的功能。

      请参阅下面的一些示例:

      // create object manually using initializer syntax
      JsonObject obj = new JsonObject
      {
          ["Id"] = 3,
          ["Name"] = "Bob",
          ["DOB"] = new DateTime(2001, 02, 03),
          ["Friends"] = new JsonArray
          {
              new JsonObject 
              {
                  ["Id"] = 2,
                  ["Name"] = "Smith"
              },
              new JsonObject
              {
                  ["Id"] = 4,
                  ["Name"] = "Jones"
              } 
          }
      };
      
      // random access to values
      int id = (int)obj["Id"];
      DateTime dob = (DateTime)obj["DOB"];
      string firstFriendName = (string)obj["Friends"][0]["Name"];
      

      下面列出了现在在.NET6 中使用System.Text.Json 变得更加容易的其他一些很酷的东西。

      解析、创建和 DOM 操作

      // parse
      JsonObject jsonObj = JsonObject.Parse(jsonString);
      

      如果你有一个JsonElement(也许在反序列化为dynamicobjectJsonElement之后)你可以调用Create,现在你有一个可导航和可写的DOM对象:

      // create
      JsonObject obj = JsonObject.Create(jsonElement);
      

      您可以添加/删除属性:

      obj.Add("FullName", "Bob Smith");
      bool successfullyRemoved = obj.Remove("Name");
      

      使用返回JsonNodeContainsKeyTryGetPropertyValue安全地询问特定键的对象:

      if (obj.ContainsKey("Hobbies"))
          // do stuff
      
      if (obj.TryGetPropertyValue("Hobbies", out JsonNode? node))
          // do stuff
      

      项目和过滤数据

      可以使用 Linq 对JsonObject 进行投影和过滤:

      // select Keys
      List<string> keys = obj.Select(node => node.Key).ToList();
      // filter friends
      var friends = obj["Friends"].AsArray()
                   .Where(n => n.AsObject().ContainsKey("Id") && (int)n.AsObject()["Id"] > 2);
      

      反序列化 Json

      现在可以轻松反序列化 Json 或反序列化 Json 的一部分。当我们只想从主对象反序列化部分 Json 时,这很有用。对于上面的示例,我们可以轻松地将朋友列表反序列化为通用 List&lt;Friend&gt;

      List<Friend> friends = obj["Friends"].AsArray().Deserialize<List<Friend>>();
      

      其中Deserilize&lt;T&gt;()JsonNode 上的扩展方法。

      序列化

      使用ToJsonString()序列化JsonObject很容易:

      string s = obj.ToJsonString();
      
      // write pretty json with WriteIndented
      string s = obj.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));
      

      在您的特定情况下,您可以在 DTO 中定义 JsonObject

      public class MyDTO
      {
          public JsonObject ExtractedData {get;set;}
      }
      

      【讨论】:

        猜你喜欢
        • 2020-07-10
        • 1970-01-01
        • 2020-02-02
        • 2020-02-16
        • 1970-01-01
        • 2019-11-03
        • 1970-01-01
        • 2020-10-03
        • 1970-01-01
        相关资源
        最近更新 更多