Как обрабатывать как отдельный элемент, так и массив для одного и того же свойства с помощью JSON.net

Я пытаюсь исправить мою библиотеку SendGridPlus для работы с событиями SendGrid, но у меня возникают некоторые проблемы с непоследовательным отношением к категориям в API.

В следующем примере полезной нагрузки, взятой из ссылки API SendGrid , вы заметите, что свойство category для каждого элемента может быть либо отдельной строкой, либо массивом строк.

 [ { "email": "[email protected]", "timestamp": 1337966815, "category": [ "newuser", "transactional" ], "event": "open" }, { "email": "[email protected]", "timestamp": 1337966815, "category": "olduser", "event": "open" } ] 

Кажется, мои возможности сделать JSON.NET подобным образом – это исправление строки перед ее входом или настройка JSON.NET для принятия неверных данных. Я бы предпочел не выполнять синтаксический анализ строк, если мне это удастся.

Есть ли другой способ, которым я могу справиться с этим, используя Json.Net?

Лучший способ справиться с этой ситуацией – использовать пользовательский JsonConverter .

Прежде чем перейти к конвертеру, нам нужно определить class для десериализации данных. Для свойства « Categories которое может варьироваться между одним элементом и массивом, определите его как List и пометьте его атрибутом [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))] public List Categories { get; set; } } 

Вот как я мог бы реализовать конвертер. Заметьте, что я сделал универсальный конвертер, чтобы его можно было использовать со строками или другими типами объектов по мере необходимости.

 class SingleOrArrayConverter : JsonConverter { public override bool CanConvert(Type objectType) { return (objectType == typeof(List)); } 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>(); } return new List { token.ToObject() }; } 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"": ""[email protected]"", ""timestamp"": 1337966815, ""category"": [ ""newuser"", ""transactional"" ], ""event"": ""open"" }, { ""email"": ""[email protected]"", ""timestamp"": 1337966815, ""category"": ""olduser"", ""event"": ""open"" } ]"; List list = JsonConvert.DeserializeObject>(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: [email protected] timestamp: 1337966815 event: open categories: newuser, transactional email: [email protected] 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 list = (List)value; if (list.Count == 1) { value = list[0]; } serializer.Serialize(writer, value); } 

Сценарий: https://dotnetfiddle.net/XG3eRy

Я работал над этим целую вечность, и благодаря Брайану за его ответ. Все, что я добавляю, это ответ 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е:

   _  _ Public Property YourLocalName As List(Of YourObject) 

Надеюсь, это сэкономит вам время

У меня была очень похожая проблема. Мой запрос Json был для меня совершенно неизвестен. Я только знал.

Там будет objectId и некоторые пары значений AND и AI.

Я использовал его для модели EAV, которую я сделал:

Мой запрос JSON:

{objectId “: 2,” firstName “:” Hans “,” email “: [” [email protected] “,” [email protected] “],” name “:” Andre “,” something “: [ 232 “,” 123 “]}

Мой class i определил:

 [JsonConverter(typeof(AnonyObjectConverter))] public class AnonymObject { public AnonymObject() { fields = new Dictionary(); list = new List(); } public string objectid { get; set; } public Dictionary fields { get; set; } public List 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», я переключаюсь в список, читаю первую строку и разделяю значения. После этого я удаляю первую запись из списка и продолжаю итерацию из Словаря.

Может быть, у кого-то такая же проблема и можно использовать это 🙂

С уважением Андре

Вы можете использовать JSONConverterAttribute как JSONConverterAttribute здесь: http://james.newtonking.com/projects/json/help/

Предполагая, что у вас есть class, который выглядит

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

Я нашел другое решение, которое может обрабатывать категорию как строку или массив, используя объект. Таким образом, мне не нужно испортить сериализатор json.

Пожалуйста, дайте ему посмотреть, есть ли у вас время и рассказать мне, что вы думаете. https://github.com/MarcelloCarreira/sendgrid-csharp-eventwebhook

Это основано на решении на https://sendgrid.com/blog/tracking-email-using-azure-sendgrid-event-webhook-part-1/, но я также добавил преобразование даты из timestamp, обновил переменные, чтобы отразить текущая модель SendGrid (и работающие категории).

Я также создал обработчик с базовым параметром auth as. См. Файлы ashx и примеры.

Спасибо!

  • Сериализация типа Json.Net с полиморфным дочерним объектом
  • Как я могу зашифровать выбранные свойства при сериализации моих объектов?
  • Линейная сериализация и де-сериализация
  • Как написать Json-файл в C #?
  • Как добавить дополнительное свойство в сериализованную строку JSON с помощью json.net?
  • Можете ли вы определить, был ли объект, десериализованный объект, отсутствием поля с classом JsonConvert в Json.NET
  • .NET NewtonSoft JSON десериализует карту для другого имени свойства
  • Json.NET добавляет обратную косую черту при возврате сериализованной строки json
  • Json.Net: Html Помощник не регенерирует
  • JSON.NET как сериализатор OAP для WebAPI 2 и ODataMediaTypeFormatter
  • Настройка пользовательских дат WebApi Json.NET
  • Interesting Posts

    Windows 7 Home висит на экране «Добро пожаловать»

    Может ли auth_user.username django быть varchar (75)? Как это можно сделать?

    Удаление непустого каталога программно в C или C ++

    Как предотвратить перемещение дополнительного пространства дисплея с места для ноутбука в macOS?

    Можно ли отделить основную функцию и classы C ++ от подпрограмм Objective-C и / или C при компиляции и ссылке?

    Самый эффективный способ найти все exe-файлы на диске с помощью C #?

    Каков самый быстрый способ сравнить два растровых изображения равного размера, чтобы определить, идентичны ли они?

    Каков самый быстрый способ чтения текстового файла по очереди?

    jquery UI Сортировка с таблицей и шириной

    Есть ли способ в Java определить, является ли путь действительным, не пытаясь создать файл?

    javascript изменение размера события стрельбы несколько раз, перетаскивая ручку изменения размера

    DependencyProperty getter / setter не вызывается

    Экспорт настроек быстрого набора из Firefox в Chrome Быстрый набор

    Как очистить или удалить параметры политики групповой политики домена после выхода из домена

    Как получить значение AM / PM из DateTime?

    Давайте будем гением компьютера.