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

我试图修复我的SendGridPlus库来处理SendGrid事件,但是我在处理类中的不一致处理方面遇到了一些麻烦。

在以下SendGrid API引用中的示例有效内容中,您会注意到每个项目的category属性可以是单个string,也可以是一个string数组。

 [ { "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的选项是修复string,或configurationJSON.NET接受不正确的数据。 我宁愿不做任何stringparsing,如果我可以摆脱它。

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

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

在我们到达转换器之前,我们需要定义一个类来反序列化数据。 对于可以在单个项目和数组之间变化的Categories属性,将其定义为List<string>并将其标记为[JsonConverter]属性,以便JSON.Net知道使用该属性的自定义转换器。 我还build议使用[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; } } 

这是我将如何实现转换器。 注意我已经使转换器是通用的,以便它可以根据需要与string或其他types的对象一起使用。

 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

我在这方面做了很多年,感谢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 

那么在你的class级里:

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

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

你可以使用JSONConverterAttribute在这里find: http : JSONConverterAttribute

假设你有一个类似的类

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

我发现另一个解决scheme,可以通过使用对象来处理string或数组的类别。 这样我就不需要搞乱json序列化器了。

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

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

我也创build了一个基本身份validation处理程序作为选项。 查看ashx文件和示例。

谢谢!

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

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

我用它做了一个EAV模型我做了:

我的JSON请求:

{objectId“:2,”firstName“:”Hans“,”email“:[”a@b.de“,”a@c.de“],”name“:”Andre“,”something“:[ 232“,”123“]}

我的class级定义:

 [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”我切换到列表,读取第一行,并拆分值。 之后,我从列表中删除第一个条目,并继续从字典的迭代。

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

问候安德烈