Как обрабатывать как отдельный элемент, так и массив для одного и того же свойства с помощью 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.
- Что такое эквивалент JSON.NET XPath, SelectNodes XML, SelectSingleNode?
- Сериализовать словарь как массив (пар ключей)
- json.net: указать конвертер для ключей словаря
- В чем разница между PreserveReferencesHandling и ReferenceLoopHandling в Json.Net?
Есть ли другой способ, которым я могу справиться с этим, используя Json.Net?
- Сериализация нулевого значения в JSON.NET
- Как десериализовать JSON с двойными именами свойств в одном и том же объекте
- Разбор JSON в C #
- Как игнорировать неизвестные значения enum во время десериализации json?
- Объединение двух массивов Json.NET путем конкатенации содержащихся элементов
- 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 и примеры.
Спасибо!