Najlepszym sposobem radzenia sobie z tą sytuacją jest użycie pliku niestandardowego JsonConverter
.
Zanim przejdziemy do konwertera, musimy zdefiniować klasę do deserializacji danych. W przypadku Categories
właściwości, która może różnić się między pojedynczym elementem a tablicą, zdefiniuj ją jako a List<string>
i oznacz [JsonConverter]
atrybutem, aby JSON.Net wiedział, że ma użyć niestandardowego konwertera dla tej właściwości. Poleciłbym również używanie [JsonProperty]
atrybutów, aby właściwościom elementów członkowskich można było nadać znaczące nazwy niezależnie od tego, co jest zdefiniowane w 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; }
}
Oto jak zaimplementowałbym konwerter. Zauważ, że utworzyłem konwerter generyczny, aby można go było używać z ciągami znaków lub innymi typami obiektów w razie potrzeby.
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();
}
}
Oto krótki program demonstrujący działanie konwertera z przykładowymi danymi:
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<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();
}
}
}
I na koniec, oto wynik powyższego:
email: [email protected]
timestamp: 1337966815
event: open
categories: newuser, transactional
email: [email protected]
timestamp: 1337966815
event: open
categories: olduser
Skrzypce: https://dotnetfiddle.net/lERrmu
EDYTOWAĆ
Jeśli potrzebujesz pójść w drugą stronę, tj. Serializować, zachowując ten sam format, możesz zaimplementować WriteJson()
metodę konwertera, jak pokazano poniżej. (Pamiętaj, aby usunąć CanWrite
nadpisanie lub zmienić je na powrót true
, w przeciwnym razie WriteJson()
nigdy nie zostanie wywołany).
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);
}
Fiddle: https://dotnetfiddle.net/XG3eRy
DeserializeObject
wywołania, jeśli używasz[JsonConverter]
atrybutu we właściwości list w swojej klasie, jak pokazano w powyższej odpowiedzi. Jeśli nie używasz atrybutu, to tak, musisz przekazać konwerter doDeserializeObject
.List<T>
w konwerterze naT[]
i zmień.Count
na.Length
. dotnetfiddle.net/vnCNgZPracowałem nad tym od wieków i dziękuję Brianowi za jego odpowiedź. Wszystko, co dodam, to odpowiedź vb.net !:
wtedy w twojej klasie:
Mam nadzieję, że zaoszczędzi ci to trochę czasu
źródło
Jako odmianę moll do wielkiego odpowiedzi przez Brian Rogers , tu są dwie wersje manipulowane
SingleOrArrayConverter<T>
.Po pierwsze, oto wersja, która działa dla wszystkich
List<T>
dla każdego typu,T
który sam nie jest zbiorem:Można go używać w następujący sposób:
Uwagi:
Konwerter pozwala uniknąć konieczności wstępnego ładowania całej wartości JSON do pamięci jako
JToken
hierarchii.Konwerter nie dotyczy list, których pozycje są również serializowane jako kolekcje, np
List<string []>
canWrite
Argument logiczny przekazany do konstruktora określa, czy należy ponownie serializować listy jednoelementowe jako wartości JSON, czy jako tablice JSON.Konwerter
ReadJson()
używaexistingValue
predefiniowanej alokacji if, aby obsługiwać zapełnianie członków listy tylko do pobierania.Po drugie, oto wersja, która działa z innymi zbiorami ogólnymi, takimi jak
ObservableCollection<T>
:Następnie, jeśli twój model używa, powiedzmy,
ObservableCollection<T>
dla niektórychT
, możesz zastosować go w następujący sposób:Uwagi:
SingleOrArrayListConverter
,TCollection
typ musi być do odczytu / zapisu i mieć konstruktora bez parametrów.Demo skrzypce z podstawowymi testami jednostkowymi tutaj .
źródło
Miałem bardzo podobny problem. Moje żądanie Json było dla mnie zupełnie nieznane. Tylko wiedziałem.
Będzie w nim identyfikator objectId i kilka par klucza anonim-wartość ORAZ tablice.
Użyłem go do modelu EAV, który zrobiłem:
Moje żądanie JSON:
Moja klasa zdefiniowała:
a teraz, gdy chcę deserializować nieznane atrybuty z ich wartością i tablicami w nim, mój konwerter wygląda tak:
Więc teraz za każdym razem, gdy otrzymuję AnonymObject, mogę iterować po Słowniku i za każdym razem, gdy pojawia się moja flaga „ValueDummyForEAV”, przełączam się na listę, czytam pierwszą linię i dzielę wartości. Następnie usuwam pierwszy wpis z listy i przechodzę do iteracji ze Słownika.
Może ktoś ma ten sam problem i może to wykorzystać :)
Pozdrawiam Andre
źródło
Możesz użyć
JSONConverterAttribute
znalezionego tutaj: http://james.newtonking.com/projects/json/help/Zakładając, że masz klasę, która wygląda jak
Udekorowałbyś właściwość kategorii, jak widać tutaj:
źródło
Aby sobie z tym poradzić, musisz użyć niestandardowego JsonConverter. Ale prawdopodobnie miałeś już to na myśli. Po prostu szukasz konwertera, którego możesz użyć od razu. A to oferuje więcej niż tylko rozwiązanie opisanej sytuacji. Podaję przykład z zadanym pytaniem.
Jak korzystać z mojego konwertera:
Umieść atrybut JsonConverter powyżej właściwości.
JsonConverter(typeof(SafeCollectionConverter))
A to mój konwerter:
A ten konwerter używa następującej klasy:
Co to dokładnie robi? Jeśli umieścisz atrybut konwertera, konwerter będzie używany dla tej właściwości. Możesz go użyć na normalnym obiekcie, jeśli spodziewasz się tablicy json z 1 lub bez wyniku. Lub używasz go w miejscu, w
IEnumerable
którym oczekujesz obiektu json lub tablicy json. (Wiedz, żearray
-object[]
- jestIEnumerable
) Wadą jest to, że ten konwerter można umieścić tylko nad właściwością, ponieważ uważa, że może przekształcić wszystko. I uważaj . Astring
jest równieżIEnumerable
.I oferuje więcej niż odpowiedź na pytanie: jeśli szukasz czegoś według id, wiesz, że otrzymasz tablicę z jednym wynikiem lub bez. Plik
ToObjectCollectionSafe<TResult>()
metoda poradzi sobie z tym za Ciebie.Jest to przydatne w przypadku pojedynczego wyniku w porównaniu z tablicą przy użyciu JSON.net i obsługuje zarówno pojedynczy element, jak i tablicę dla tej samej właściwości i może konwertować tablicę na pojedynczy obiekt.
Zrobiłem to dla żądań REST na serwerze z filtrem, który zwrócił jeden wynik w tablicy, ale chciałem otrzymać wynik z powrotem jako pojedynczy obiekt w moim kodzie. A także dla odpowiedzi wynikowej OData z rozszerzonym wynikiem z jednym elementem w tablicy.
Baw się dobrze.
źródło
Znalazłem inne rozwiązanie, które może obsłużyć kategorię jako ciąg lub tablicę za pomocą obiektu. W ten sposób nie muszę mieszać z serializatorem json.
Popatrz, jeśli masz czas, i powiedz mi, co myślisz. https://github.com/MarcelloCarreira/sendgrid-csharp-eventwebhook
Opiera się na rozwiązaniu na https://sendgrid.com/blog/tracking-email-using-azure-sendgrid-event-webhook-part-1/ ale dodałem także konwersję daty z sygnatury czasowej, zaktualizowałem zmienne, aby odzwierciedlały aktualny model SendGrid (i działające kategorie).
Stworzyłem również handler z podstawową autoryzacją jako opcją. Zobacz pliki ashx i przykłady.
Dziękuję Ci!
źródło