System.Text.Json中的JObject相当于什么?

74

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

public class MyDTO
{
    public JObject ExtractedData {get;set;}
}
我正在将此项目转换为.NET 5。在.NET 5中,等同于JObject的是什么?我尝试避免使用JsonDocument,因为(来自文档):

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

我计划使用JsonElement。这是最合适的选择还是有其他类型可用来保存JSON作为对象?

2个回答

92

截至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
var jsonObj = JsonNode.Parse(jsonString).AsObject();
如果您有一个JsonElement(可能是在反序列化为dynamic、object或JsonElement后),您可以调用Create,现在您拥有了一个可导航和可写的DOM对象:
// create
JsonObject obj = JsonObject.Create(jsonElement);

您可以添加/删除属性:

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

使用ContainsKeyTryGetPropertyValue(返回一个JsonNode)来安全地查询对象是否包含特定的键:

if (obj.ContainsKey("Hobbies"))
    // do stuff

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

项目和过滤数据

使用 Linq 可以对 JsonObject 进行项目投影和过滤。

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

反序列化Json

现在对于Json的反序列化或者部分反序列化已经变得十分容易。这用于当我们只需要从主对象中反序列化一部分Json时非常有用。对于上面的例子,我们可以将朋友列表简单地反序列化为一个通用的List<Friend>

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

其中Deserilize<T>()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;}
}

1
这应该被标记为答案。 - DanielV

27
在.NET 5和.NET Core 3.1中,与JObject最接近的等价物确实是JsonElement,因此您可以按以下方式修改DTO:
public class MyDTO
{
    public JsonElement ExtractedData {get;set;}
}

不必担心处置任何文档,因为在内部使用的JsonSerializerJsonElementConverter返回非池元素(通过在.NET 5中克隆元素)。

但是请注意以下内容:

  1. JsonElement represents any JSON value and thus corresponds most closely to JToken not JObject. As JsonElement is a struct there is no subclass corresponding to a JSON object. If you want to constrain ExtractedData to be a JSON object you will need to check this in the 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. Since JsonElement is a struct, the default value is not null. So, what is it? It turns out that default(JsonElement) has ValueKind = JsonValueKind.Undefined:

    There is no value (as distinct from Null).

    If you attempt to serialize such a default JsonElement with JsonSerializer, an exception will be thrown. I.e. if you simply do

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

    Then a System.InvalidOperationException: Operation is not valid due to the current state of the object. exception is thrown.

    You have a few options to avoid this problem:

    • In .NET 5 you can apply [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] like so:

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

      This causes uninitialized values of ExtractedData to be skipped during serialization.

    • In .NET Core 3.x JsonIgnoreCondition does not exist, so you could instead define ExtractedData to be nullable:

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

      Or you could initialize it to a null JsonElement like so:

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

      Both options cause uninitialized values of ExtractedData to serialize as null.

  3. See also the related questions:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接