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

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

В следующем примере полезной нагрузки, взятой из ссылки API SendGrid , вы заметите, что свойство 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?

5 Solutions collect form web for “Как обрабатывать как отдельный элемент, так и массив для одного и того же свойства с помощью 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"": ""john.doe@sendgrid.com"", ""timestamp"": 1337966815, ""category"": [ ""newuser"", ""transactional"" ], ""event"": ""open"" }, { ""email"": ""jane.doe@sendgrid.com"", ""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: 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 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е:

  

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

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

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

Мой запрос JSON:

{objectId “: 2,” firstName “:” Hans “,” email “: [” a@b.de “,” a@c.de “],” 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 и примеры.

Спасибо!

Interesting Posts

Принудительное удаление каталога под Win 7

Как я могу передавать одну и ту же музыку на несколько компьютеров?

Excel VBA – добавление элемента в конец массива

Ошибка5: доступ запрещен при перезапуске службы журнала событий

Создают ли браузеры новые TCP-соединения для каждого HTTP-запроса?

Скрыть расширения от хрома без нажатия на хромированную панель

Как удалить деревья каталогов через пакетный файл в Windows 7?

Как синхронизировать закладки между Firefox, Chrome и Safari на нескольких компьютерах?

Остановите Windows 7 Explorer от автоматического расширения папок

Предотвращение установки обновлений Windows 10 от Windows 7

Хорошая графическая карта важна для удаленного рабочего стола?

Любой способ изменения разрешения экрана Windows через командную строку?

Какие вещи можно безопасно удалить или удалить из новой установки Windows 7?

Блокнот + блок + раскрыть / свернуть блок с помощью клавиатуры

«Большой тип» на Mac, например, в приложении адресной книги

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