如何使用 JSON.net 处理同一属性的单个项目和数组

Posted

技术标签:

【中文标题】如何使用 JSON.net 处理同一属性的单个项目和数组【英文标题】:How to handle both a single item and an array for the same property using JSON.net 【发布时间】:2013-09-30 10:35:01 【问题描述】:

我正在尝试修复我的 SendGridPlus 库以处理 SendGrid 事件,但我在 API 中对类别的处理不一致时遇到了一些问题。

在以下取自SendGrid API reference 的有效负载示例中,您会注意到每个项目的category 属性可以是单个字符串或字符串数​​组。

[
  
    "email": "john.doe@sendgrid.com",
    "timestamp": 1337966815,
    "category": [
      "newuser",
      "transactional"
    ],
    "event": "open"
  ,
  
    "email": "jane.doe@sendgrid.com",
    "timestamp": 1337966815,
    "category": "olduser",
    "event": "open"
  
]

似乎我的选择是让 JSON.NET 像这样在输入之前修复字符串,或者配置 JSON.NET 以接受不正确的数据。如果可以的话,我宁愿不做任何字符串解析。

有没有其他方法可以使用 Json.Net 来处理这个问题?

【问题讨论】:

【参考方案1】:

处理这种情况的最佳方法是使用自定义JsonConverter

在我们使用转换器之前,我们需要定义一个类来反序列化数据。对于可以在单个项目和数组之间变化的 Categories 属性,将其定义为 List<string> 并用 [JsonConverter] 属性标记它,以便 JSON.Net 知道使用该属性的自定义转换器。我还建议使用[JsonProperty] 属性,以便可以为成员属性赋予有意义的名称,而与 JSON 中定义的内容无关。

class Item

    [JsonProperty("email")]
    public string Email  get; set; 

    [JsonProperty("timestamp")]
    public int Timestamp  get; set; 

    [JsonProperty("event")]
    public string Event  get; set; 

    [JsonProperty("category")]
    [JsonConverter(typeof(SingleOrArrayConverter<string>))]
    public List<string> Categories  get; set; 

这是我将如何实现转换器。请注意,我已将转换器设为通用,以便它可以根据需要与字符串或其他类型的对象一起使用。

class SingleOrArrayConverter<T> : JsonConverter

    public override bool CanConvert(Type objectType)
    
        return (objectType == typeof(List<T>));
    

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    
        JToken token = JToken.Load(reader);
        if (token.Type == JTokenType.Array)
        
            return token.ToObject<List<T>>();
        
        return new List<T>  token.ToObject<T>() ;
    

    public override bool CanWrite
    
        get  return false; 
    

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    
        throw new NotImplementedException();
    

这是一个简短的程序,演示了转换器与您的示例数据的作用:

class Program

    static void Main(string[] args)
    
        string json = @"
        [
          
            ""email"": ""john.doe@sendgrid.com"",
            ""timestamp"": 1337966815,
            ""category"": [
              ""newuser"",
              ""transactional""
            ],
            ""event"": ""open""
          ,
          
            ""email"": ""jane.doe@sendgrid.com"",
            ""timestamp"": 1337966815,
            ""category"": ""olduser"",
            ""event"": ""open""
          
        ]";

        List<Item> list = JsonConvert.DeserializeObject<List<Item>>(json);

        foreach (Item obj in list)
        
            Console.WriteLine("email: " + obj.Email);
            Console.WriteLine("timestamp: " + obj.Timestamp);
            Console.WriteLine("event: " + obj.Event);
            Console.WriteLine("categories: " + string.Join(", ", obj.Categories));
            Console.WriteLine();
        
    

最后,这是上面的输出:

email: john.doe@sendgrid.com
timestamp: 1337966815
event: open
categories: newuser, transactional

email: jane.doe@sendgrid.com
timestamp: 1337966815
event: open
categories: olduser

小提琴:https://dotnetfiddle.net/lERrmu

编辑

如果你需要另一种方式,即序列化,同时保持相同的格式,你可以实现转换器的WriteJson()方法,如下所示。 (请务必删除 CanWrite 覆盖或将其更改为返回 true,否则永远不会调用 WriteJson()。)

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    
        List<T> list = (List<T>)value;
        if (list.Count == 1)
        
            value = list[0];
        
        serializer.Serialize(writer, value);
    

小提琴:https://dotnetfiddle.net/XG3eRy

【讨论】:

完美!你就是那个男人。幸运的是,我已经完成了有关使用 JsonProperty 使属性更有意义的所有其他工作。感谢您提供令人惊讶的完整答案。 :) 没问题;很高兴你发现它有帮助。 太棒了!这就是我一直在寻找的。 @BrianRogers,如果你曾经在阿姆斯特丹,我会喝酒! @israelaltar 如果您在类中的 list 属性上使用 [JsonConverter] 属性,则无需将转换器添加到 DeserializeObject 调用中,如上面的答案所示。如果您使用该属性,那么,是的,您需要将转换器传递给DeserializeObject @ShaunLangley 要使转换器使用数组而不是列表,请将转换器中对List&lt;T&gt; 的所有引用更改为T[],并将.Count 更改为.Length。 dotnetfiddle.net/vnCNgZ【参考方案2】:

我为此工作了很长时间,感谢 Brian 的回答。 我要添加的只是 vb.net 的答案!:

Public Class SingleValueArrayConverter(Of T)
sometimes-array-and-sometimes-object
    Inherits JsonConverter
    Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
        Throw New NotImplementedException()
    End Sub

    Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
        Dim retVal As Object = New [Object]()
        If reader.TokenType = JsonToken.StartObject Then
            Dim instance As T = DirectCast(serializer.Deserialize(reader, GetType(T)), T)
            retVal = New List(Of T)() From  _
                instance _
            
        ElseIf reader.TokenType = JsonToken.StartArray Then
            retVal = serializer.Deserialize(reader, objectType)
        End If
        Return retVal
    End Function
    Public Overrides Function CanConvert(objectType As Type) As Boolean
        Return False
    End Function
End Class

然后在你的班级:

 <JsonProperty(PropertyName:="JsonName)> _
 <JsonConverter(GetType(SingleValueArrayConverter(Of YourObject)))> _
    Public Property YourLocalName As List(Of YourObject)

希望这可以为您节省一些时间

【讨论】:

错字: _ Public Property YourLocalName As List(Of YourObject)【参考方案3】:

作为Brian Rogers 对great answer 的一个小改动,这里是SingleOrArrayConverter&lt;T&gt; 的两个调整版本。

首先,这是一个适用于所有List&lt;T&gt; 的版本,适用于每个类型T,它本身不是一个集合:

public class SingleOrArrayListConverter : JsonConverter

    // Adapted from this answer https://***.com/a/18997172
    // to https://***.com/questions/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n
    // by Brian Rogers https://***.com/users/10263/brian-rogers
    readonly bool canWrite;
    readonly IContractResolver resolver;

    public SingleOrArrayListConverter() : this(false)  

    public SingleOrArrayListConverter(bool canWrite) : this(canWrite, null)  

    public SingleOrArrayListConverter(bool canWrite, IContractResolver resolver)
    
        this.canWrite = canWrite;
        // Use the global default resolver if none is passed in.
        this.resolver = resolver ?? new JsonSerializer().ContractResolver;
    

    static bool CanConvert(Type objectType, IContractResolver resolver)
    
        Type itemType;
        JsonArrayContract contract;
        return CanConvert(objectType, resolver, out itemType, out contract);
    

    static bool CanConvert(Type objectType, IContractResolver resolver, out Type itemType, out JsonArrayContract contract)
    
        if ((itemType = objectType.GetListItemType()) == null)
        
            itemType = null;
            contract = null;
            return false;
        
        // Ensure that [JsonObject] is not applied to the type.
        if ((contract = resolver.ResolveContract(objectType) as JsonArrayContract) == null)
            return false;
        var itemContract = resolver.ResolveContract(itemType);
        // Not implemented for jagged arrays.
        if (itemContract is JsonArrayContract)
            return false;
        return true;
    

    public override bool CanConvert(Type objectType)  return CanConvert(objectType, resolver); 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    
        Type itemType;
        JsonArrayContract contract;

        if (!CanConvert(objectType, serializer.ContractResolver, out itemType, out contract))
            throw new JsonSerializationException(string.Format("Invalid type for 0: 1", GetType(), objectType));
        if (reader.MoveToContent().TokenType == JsonToken.Null)
            return null;
        var list = (IList)(existingValue ?? contract.DefaultCreator());
        if (reader.TokenType == JsonToken.StartArray)
            serializer.Populate(reader, list);
        else
            // Here we take advantage of the fact that List<T> implements IList to avoid having to use reflection to call the generic Add<T> method.
            list.Add(serializer.Deserialize(reader, itemType));
        return list;
    

    public override bool CanWrite  get  return canWrite;  

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    
        var list = value as ICollection;
        if (list == null)
            throw new JsonSerializationException(string.Format("Invalid type for 0: 1", GetType(), value.GetType()));
        // Here we take advantage of the fact that List<T> implements IList to avoid having to use reflection to call the generic Count method.
        if (list.Count == 1)
        
            foreach (var item in list)
            
                serializer.Serialize(writer, item);
                break;
            
        
        else
        
            writer.WriteStartArray();
            foreach (var item in list)
                serializer.Serialize(writer, item);
            writer.WriteEndArray();
        
    


public static partial class JsonExtensions

    public static JsonReader MoveToContent(this JsonReader reader)
    
        while ((reader.TokenType == JsonToken.Comment || reader.TokenType == JsonToken.None) && reader.Read())
            ;
        return reader;
    

    internal static Type GetListItemType(this Type type)
    
        // Quick reject for performance
        if (type.IsPrimitive || type.IsArray || type == typeof(string))
            return null;
        while (type != null)
        
            if (type.IsGenericType)
            
                var genType = type.GetGenericTypeDefinition();
                if (genType == typeof(List<>))
                    return type.GetGenericArguments()[0];
            
            type = type.BaseType;
        
        return null;
    

可以这样使用:

var settings = new JsonSerializerSettings

    // Pass true if you want single-item lists to be reserialized as single items
    Converters =  new SingleOrArrayListConverter(true) ,
;
var list = JsonConvert.DeserializeObject<List<Item>>(json, settings);

注意事项:

转换器无需将整个 JSON 值作为 JToken 层次结构预加载到内存中。

转换器不适用于其项目也被序列化为集合的列表,例如List&lt;string []&gt;

传递给构造函数的布尔 canWrite 参数控制是否将单元素列表重新序列化为 JSON 值或 JSON 数组。

转换器的ReadJson() 使用existingValue(如果已预先分配)以支持填充仅获取列表成员。

其次,这是一个适用于其他通用集合的版本,例如ObservableCollection&lt;T&gt;

public class SingleOrArrayCollectionConverter<TCollection, TItem> : JsonConverter
    where TCollection : ICollection<TItem>

    // Adapted from this answer https://***.com/a/18997172
    // to https://***.com/questions/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n
    // by Brian Rogers https://***.com/users/10263/brian-rogers
    readonly bool canWrite;

    public SingleOrArrayCollectionConverter() : this(false)  

    public SingleOrArrayCollectionConverter(bool canWrite)  this.canWrite = canWrite; 

    public override bool CanConvert(Type objectType)
    
        return typeof(TCollection).IsAssignableFrom(objectType);
    

    static void ValidateItemContract(IContractResolver resolver)
    
        var itemContract = resolver.ResolveContract(typeof(TItem));
        if (itemContract is JsonArrayContract)
            throw new JsonSerializationException(string.Format("Item contract type 0 not supported.", itemContract));
    

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    
        ValidateItemContract(serializer.ContractResolver);
        if (reader.MoveToContent().TokenType == JsonToken.Null)
            return null;
        var list = (ICollection<TItem>)(existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator());
        if (reader.TokenType == JsonToken.StartArray)
            serializer.Populate(reader, list);
        else
            list.Add(serializer.Deserialize<TItem>(reader));
        return list;
    

    public override bool CanWrite  get  return canWrite;  

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    
        ValidateItemContract(serializer.ContractResolver);
        var list = value as ICollection<TItem>;
        if (list == null)
            throw new JsonSerializationException(string.Format("Invalid type for 0: 1", GetType(), value.GetType()));
        if (list.Count == 1)
        
            foreach (var item in list)
            
                serializer.Serialize(writer, item);
                break;
            
        
        else
        
            writer.WriteStartArray();
            foreach (var item in list)
                serializer.Serialize(writer, item);
            writer.WriteEndArray();
        
    

然后,如果您的模型使用 ObservableCollection&lt;T&gt; 来表示某些 T,您可以按如下方式应用它:

class Item

    public string Email  get; set; 
    public int Timestamp  get; set; 
    public string Event  get; set; 

    [JsonConverter(typeof(SingleOrArrayCollectionConverter<ObservableCollection<string>, string>))]
    public ObservableCollection<string> Category  get; set; 

注意事项:

除了SingleOrArrayListConverter 的注意事项和限制之外,TCollection 类型必须是可读写的并且具有无参数的构造函数。

演示基本单元测试here。

【讨论】:

【参考方案4】:

要处理此问题,您必须使用自定义 JsonConverter。但你可能已经想到了这一点。 您只是在寻找可以立即使用的转换器。这不仅为所描述的情况提供了解决方案。 我举一个问题的例子。

如何使用我的转换器:

在属性上方放置一个 JsonConverter 属性。 JsonConverter(typeof(SafeCollectionConverter))

public class SendGridEvent

    [JsonProperty("email")]
    public string Email  get; set; 

    [JsonProperty("timestamp")]
    public long Timestamp  get; set; 

    [JsonProperty("category"), JsonConverter(typeof(SafeCollectionConverter))]
    public string[] Category  get; set; 

    [JsonProperty("event")]
    public string Event  get; set; 

这是我的转换器:

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;

namespace ***.question18994685

    public class SafeCollectionConverter : JsonConverter
    
        public override bool CanConvert(Type objectType)
        
            return true;
        

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        
            //This not works for Populate (on existingValue)
            return serializer.Deserialize<JToken>(reader).ToObjectCollectionSafe(objectType, serializer);
             

        public override bool CanWrite => false;

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        
            throw new NotImplementedException();
        
    

这个转换器使用以下类:

using System;

namespace Newtonsoft.Json.Linq

    public static class SafeJsonConvertExtensions
    
        public static object ToObjectCollectionSafe(this JToken jToken, Type objectType)
        
            return ToObjectCollectionSafe(jToken, objectType, JsonSerializer.CreateDefault());
        

        public static object ToObjectCollectionSafe(this JToken jToken, Type objectType, JsonSerializer jsonSerializer)
        
            var expectArray = typeof(System.Collections.IEnumerable).IsAssignableFrom(objectType);

            if (jToken is JArray jArray)
            
                if (!expectArray)
                
                    //to object via singel
                    if (jArray.Count == 0)
                        return JValue.CreateNull().ToObject(objectType, jsonSerializer);

                    if (jArray.Count == 1)
                        return jArray.First.ToObject(objectType, jsonSerializer);
                
            
            else if (expectArray)
            
                //to object via JArray
                return new JArray(jToken).ToObject(objectType, jsonSerializer);
            

            return jToken.ToObject(objectType, jsonSerializer);
        

        public static T ToObjectCollectionSafe<T>(this JToken jToken)
        
            return (T)ToObjectCollectionSafe(jToken, typeof(T));
        

        public static T ToObjectCollectionSafe<T>(this JToken jToken, JsonSerializer jsonSerializer)
        
            return (T)ToObjectCollectionSafe(jToken, typeof(T), jsonSerializer);
        
    

它到底是做什么的? 如果您放置转换器属性,转换器将用于此属性。如果您期望 json 数组有 1 或没有结果,则可以在普通对象上使用它。或者你在 IEnumerable 上使用它,你期望一个 json 对象或 json 数组。 (知道array -object[]- 是IEnumerable) 一个缺点是这个转换器只能放在一个属性之上,因为他认为他可以转换所有东西。并被警告string 也是 IEnumerable

它提供的不仅仅是问题的答案: 如果您通过 id 搜索某些内容,您知道您将得到一个返回一个或没有结果的数组。 ToObjectCollectionSafe&lt;TResult&gt;() 方法可以为您处理。

这可用于使用 JSON.net 的单一结果与数组 并处理同一属性的单个项目和数组 并且可以将数组转换为单个对象。

我为服务器上的 REST 请求做了这个,过滤器在数组中返回一个结果,但希望在我的代码中将结果作为单个对象返回。也适用于具有扩展结果的 OData 结果响应,其中包含数组中的一项。

玩得开心。

【讨论】:

【参考方案5】:

只是想在上面的 SingleOrArrayCollectionConverter 上添加到 @dbc 出色的响应。我能够修改它以与来自 HTTP 客户端的流一起使用。这是一个 sn-p(您必须设置 requestUrl(字符串)和 httpClient(使用 System.Net.Http;)。

public async Task<IList<T>> HttpRequest<T>(HttpClient httpClient, string requestedUrl, CancellationToken cancellationToken)
    
       using (var request = new HttpRequestMessage(HttpMethod.Get, requestedUrl))
       using (var httpResponseMessage = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
       
          if (httpResponseMessage.IsSuccessStatusCode)
          
             using var stream = await httpResponseMessage.Content.ReadAsStreamAsync();    
             using var streamReader = new StreamReader(stream);
             using var jsonTextReader = new JsonTextReader(streamReader );
             var settings = new JsonSerializerSettings
             
                // Pass true if you want single-item lists to be reserialized as single items
                Converters =  new SingleOrArrayCollectionConverter(true) ,
             ;
             var jsonSerializer = JsonSerializer.Create(settings);
             return jsonSerializer.Deserialize<List<T>>(jsonTextReader);
     

如果缺少括号或拼写错误,我深表歉意,在这里粘贴代码并不容易。

【讨论】:

【参考方案6】:

我有一个非常相似的问题。 我的 Json 请求对我来说是完全未知的。 我只知道。

里面会有一个objectId和一些匿名键值对和数组。

我将它用于我做过的 EAV 模型:

我的 JSON 请求:

objectId": 2, “名字”:“汉斯”, “电子邮件”:[“a@b.de”,“a@c.de”], “名称”:“安德烈”, “某事”:[“232”,“123”]

我定义的班级:

[JsonConverter(typeof(AnonyObjectConverter))]
public class AnonymObject

    public AnonymObject()
    
        fields = new Dictionary<string, string>();
        list = new List<string>();
    

    public string objectid  get; set; 
    public Dictionary<string, string> fields  get; set; 
    public List<string> list  get; set; 

现在我想反序列化未知属性及其值和数组,我的转换器看起来像这样:

   public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    
        AnonymObject anonym = existingValue as AnonymObject ?? new AnonymObject();
        bool isList = false;
        StringBuilder listValues = new StringBuilder();

        while (reader.Read())
        
            if (reader.TokenType == JsonToken.EndObject) continue;

            if (isList)
            
                while (reader.TokenType != JsonToken.EndArray)
                
                    listValues.Append(reader.Value.ToString() + ", ");

                    reader.Read();
                
                anonym.list.Add(listValues.ToString());
                isList = false;

                continue;
            

            var value = reader.Value.ToString();

            switch (value.ToLower())
            
                case "objectid":
                    anonym.objectid = reader.ReadAsString();
                    break;
                default:
                    string val;

                    reader.Read();
                    if(reader.TokenType == JsonToken.StartArray)
                    
                        isList = true;
                        val = "ValueDummyForEAV";
                    
                    else
                    
                        val = reader.Value.ToString();
                    
                    try
                    
                        anonym.fields.Add(value, val);
                    
                    catch(ArgumentException e)
                    
                        throw new ArgumentException("Multiple Attribute found");
                    
                    break;
            

        

        return anonym;
    

所以现在每次我得到一个 AnonymObject 时,我都可以遍历字典,并且每次有我的标志“ValueDummyForEAV”时,我都会切换到列表,读取第一行并拆分值。之后我从列表中删除第一个条目并继续从字典中迭代。

也许有人有同样的问题,可以使用这个:)

问候 安德烈

【讨论】:

【参考方案7】:

您可以使用JSONConverterAttribute,如下所示:http://james.newtonking.com/projects/json/help/

假设你有一个看起来像这样的类

public class RootObject

    public string email  get; set; 
    public int timestamp  get; set; 
    public string smtpid  get; set; 
    public string @event  get; set; 
    public string category[]  get; set; 

您将装饰类别属性,如下所示:

    [JsonConverter(typeof(SendGridCategoryConverter))]
    public string category  get; set; 

public class SendGridCategoryConverter : JsonConverter

  public override bool CanConvert(Type objectType)
  
    return true; // add your own logic
  

  public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  
   // do work here to handle returning the array regardless of the number of objects in 
  

  public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  
    // Left as an exercise to the reader :)
    throw new NotImplementedException();
  

【讨论】:

谢谢你,但它仍然不能解决问题。当一个实际的数组进入时,它仍然会在我的代码甚至可以为具有实际数组的对象执行之前引发错误。 '附加信息:反序列化对象时出现意外标记:字符串。路径'[2].category[0]',第 17 行,位置 27。' 私有字符串有效负载 = "[\n" + "\n" + "\"email\": \"john.doe@sendgrid.com\",\n" + "\"时间戳\": 1337966815,\n" + "\"smtp-id\": \"\",\n" + "\"category\": \"newuser\", \n" + "\"事件\": \"点击\"\n" + ", " + ""+ "\"email\": \"john.doe@sendgrid.com\",\n " + "\"时间戳\": 1337969592,\n" + "\"smtp-id\": \"\",\n" + "\"类别\" : [\"somestring1\",\"somestring2\"],\n" + "\"event\": \"processed\",\n" + "\n" + "]"; 它很好地处理了第一个对象并且很好地处理了没有数组。但是当我为第二个对象创建一个数组时,它失败了。 @AdvancedREI 没有看到您的代码,我猜想您在阅读 JSON 后会让阅读器的位置不正确。与其尝试直接使用阅读器,不如从阅读器加载 JToken 对象并从那里开始。有关转换器的工作实现,请参阅我的答案。 Brian 的回答中的细节要好得多。使用那个:)【参考方案8】:

我找到了另一种解决方案,可以通过使用 object 将类别处理为字符串或数组。这样我就不需要搞乱 json 序列化程序了。

如果你有时间,请看看,告诉我你的想法。 https://github.com/MarcelloCarreira/sendgrid-csharp-eventwebhook

它基于https://sendgrid.com/blog/tracking-email-using-azure-sendgrid-event-webhook-part-1/ 的解决方案,但我还添加了时间戳的日期转换,升级了变量以反映当前的 SendGrid 模型(并使类别工作)。

我还创建了一个带有基本身份验证选项的处理程序。请参阅 ashx 文件和示例。

谢谢!

【讨论】:

以上是关于如何使用 JSON.net 处理同一属性的单个项目和数组的主要内容,如果未能解决你的问题,请参考以下文章

Json.NET:单个属性的不同JSON模式

如何参考CoreData中同一实体的单个属性更新实体的属性?

如何从 JSON.NET 的 JsonArray 中提取单个数组成员?

序列化特定类型时如何使 JSON.Net 序列化程序调用 ToString()?

如何使用 Json.NET 更改 JSON 属性的值? [关闭]

如何使用 json.net 将 json 数组添加到 JObject 的属性中