Лучший способ справиться с этой ситуацией - использовать собственный JsonConverter
.
Прежде чем мы перейдем к конвертеру, нам нужно определить класс для десериализации данных. Для Categories
свойства, которое может различаться между отдельным элементом и массивом, определите его как a 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
DeserializeObject
вызову, если вы используете[JsonConverter]
атрибут в свойстве list в своем классе, как показано в ответе выше. Если вы не используете атрибут, то да, вам нужно передать конвертер вDeserializeObject
.List<T>
в преобразователе наT[]
и измените.Count
на.Length
. dotnetfiddle.net/vnCNgZЯ работал над этим целую вечность, и спасибо Брайану за его ответ. Все, что я добавляю, это ответ vb.net !:
тогда в вашем классе:
Надеюсь, это сэкономит вам время
источник
В качестве небольшого изменения в большой ответ по Брайан Роджерс , вот две Подправлены версии
SingleOrArrayConverter<T>
.Во-первых, вот версия, которая работает для всех
List<T>
типов,T
которые сами по себе не являются коллекцией:Его можно использовать следующим образом:
Ноты:
Конвертер избавляет от необходимости предварительно загружать все значение JSON в память в виде
JToken
иерархии.Конвертер не применяется к спискам, элементы которых также сериализованы как коллекции, например
List<string []>
Логический
canWrite
аргумент, переданный конструктору, определяет, следует ли повторно сериализовать одноэлементные списки как значения JSON или как массивы JSON.Преобразователь
ReadJson()
используетexistingValue
предварительно выделенный if, чтобы поддерживать заполнение членов списка только для получения.Во-вторых, вот версия, которая работает с другими общими коллекциями, такими как
ObservableCollection<T>
:Затем, если ваша модель использует, скажем,
ObservableCollection<T>
для некоторыхT
, вы можете применить его следующим образом:Ноты:
SingleOrArrayListConverter
,TCollection
тип должен быть доступен для чтения и записи и иметь конструктор без параметров.Демонстрационная скрипка с основными модульными тестами здесь .
источник
У меня была очень похожая проблема. Мой Json Request был мне совершенно неизвестен. Я только знал.
В нем будет objectId и несколько анонимных пар ключ-значение И массивов.
Я использовал его для модели EAV, которую я сделал:
Мой запрос JSON:
Мой класс я определил:
и теперь, когда я хочу десериализовать неизвестные атрибуты с их значением и массивами в нем, мой конвертер выглядит так:
Итак, теперь каждый раз, когда я получаю AnonymObject, я могу перебирать словарь, и каждый раз, когда появляется мой флаг «ValueDummyForEAV», я переключаюсь на список, читаю первую строку и разделяю значения. После этого я удаляю первую запись из списка и продолжаю итерацию из Словаря.
Может у кого-то такая же проблема и может это использовать :)
С уважением, Андре
источник
Вы можете использовать
JSONConverterAttribute
здесь: http://james.newtonking.com/projects/json/help/Предполагая, что у вас есть класс, который выглядит как
Вы бы украсили свойство категории, как показано здесь:
источник
Чтобы справиться с этим, вы должны использовать собственный JsonConverter. Но вы, вероятно, уже имели это в виду. Вы просто ищете конвертер, который можно сразу использовать. И это предлагает больше, чем просто решение описанной ситуации. Приведу пример с заданным вопросом.
Как использовать мой конвертер:
Поместите атрибут JsonConverter над свойством.
JsonConverter(typeof(SafeCollectionConverter))
А это мой конвертер:
И этот конвертер использует следующий класс:
Что именно он делает? Если вы поместите атрибут конвертера, конвертер будет использоваться для этого свойства. Вы можете использовать его для обычного объекта, если вы ожидаете, что массив json будет иметь 1 или нет результата. Или вы используете его там,
IEnumerable
где вы ожидаете объект json или массив json. (Знайте, чтоarray
-object[]
- этоIEnumerable
). Недостатком является то, что этот преобразователь может быть размещен только над объектом, поскольку он думает, что может преобразовать все. И будьте осторожны . Аstring
такжеIEnumerable
.И он предлагает больше, чем ответ на вопрос: если вы ищете что-то по идентификатору, вы знаете, что получите обратно массив с одним результатом или без результата. В
ToObjectCollectionSafe<TResult>()
Метод может справиться с этим для вас.Это можно использовать для Single Result vs Array с использованием JSON.net и обрабатывать как один элемент, так и массив для одного и того же свойства, а также можно преобразовать массив в один объект.
Я сделал это для запросов REST на сервере с фильтром, который возвращал один результат в массиве, но хотел получить результат обратно как один объект в моем коде. А также для ответа результата OData с расширенным результатом с одним элементом в массиве.
Получайте удовольствие от этого.
источник
Я нашел другое решение, которое может обрабатывать категорию как строку или массив с помощью объекта. Таким образом, мне не нужно путаться с сериализатором json.
Пожалуйста, взгляните на него, если у вас есть время, и скажите мне, что вы думаете. https://github.com/MarcelloCarreira/sendgrid-csharp-eventwebhook
Он основан на решении https://sendgrid.com/blog/tracking-email-using-azure-sendgrid-event-webhook-part-1/ но я также добавил преобразование даты из метки времени, обновил переменные, чтобы отразить текущая модель SendGrid (и категории работают).
Я также создал обработчик с базовой аутентификацией в качестве опции. См. Файлы ashx и примеры.
Спасибо!
источник