Json.net serializować / deserializować typy pochodne?

99

json.net (newtonsoft)
Przeglądam dokumentację, ale nie mogę znaleźć nic na ten temat lub najlepszy sposób, aby to zrobić.

public class Base
{
    public string Name;
}
public class Derived : Base
{
    public string Something;
}

JsonConvert.Deserialize<List<Base>>(text);

Teraz mam obiekty pochodne na liście serializowanej. Jak deserializować listę i odzyskać typy pochodne?

Będzie
źródło
Nie tak działa dziedziczenie. Możesz określić JsonConvert.Deserialize <Derived> (tekst); aby uwzględnić pole Nazwa. Ponieważ Derived IS A Base (nie na odwrót), Base nie wie nic o definicji Derived.
M.Babcock,
Przepraszam, trochę wyjaśniono. Problem polega na tym, że mam listę, która zawiera zarówno obiekty podstawowe, jak i pochodne. Muszę więc dowiedzieć się, jak powiedzieć newtonsoft, jak deserializować elementy pochodne.
Będzie
Rozwiązałem to. Mam ten sam problem
Luis Carlos Chavarría

Odpowiedzi:

46

Jeśli przechowujesz typ w swoim text(tak jak powinno być w tym scenariuszu), możesz użyć rozszerzenia JsonSerializerSettings.

Zobacz: jak deserializować JSON do IEnumerable <BaseType> z Newtonsoft JSON.NET

Uważaj jednak. Używanie czegokolwiek innego niż TypeNameHandling = TypeNameHandling.Nonemoże narazić Cię na lukę w zabezpieczeniach .

kamranicus
źródło
24
Możesz również użyć TypeNameHandling = TypeNameHandling.Auto- spowoduje to dodanie $typewłaściwości TYLKO dla instancji, w których zadeklarowany typ (tj. Base) Nie pasuje do typu instancji (tj Derived.). W ten sposób nie nadyma tak bardzo twojego JSON, jak TypeNameHandling.All.
AJ Richardson,
Ciągle otrzymuję błąd rozwiązywania typu określonego w JSON „..., ...”. Ścieżka „$ type”, wiersz 1, pozycja 82. Jakieś pomysły?
briba,
3
Zachowaj ostrożność podczas korzystania z tego na publicznym punkcie końcowym, ponieważ powoduje to problemy z bezpieczeństwem: alphabot.com/security/blog/2017/net/…
gjvdkamp
1
@gjvdkamp JEEZ dzięki za to, nie wiedziałem o tym. Dodam do mojego postu.
kamranicus
96

Musisz włączyć obsługę nazw typów i przekazać to do (de) serializatora jako parametr ustawień.

Base object1 = new Base() { Name = "Object1" };
Derived object2 = new Derived() { Something = "Some other thing" };
List<Base> inheritanceList = new List<Base>() { object1, object2 };

JsonSerializerSettings settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
string Serialized = JsonConvert.SerializeObject(inheritanceList, settings);
List<Base> deserializedList = JsonConvert.DeserializeObject<List<Base>>(Serialized, settings);

Spowoduje to poprawną deserializację klas pochodnych. Wadą jest to, że nada nazwy wszystkim używanym obiektom, w związku z czym nada nazwę liście, na którą umieszczasz obiekty.

Madmenyo
źródło
31
+1. Szukałem w Google przez 30 minut, aż faktycznie dowiedziałem się, że musisz użyć tych samych ustawień dla SerializeObject i DeserializeObject. Założyłem, że użyje niejawnie typu $, jeśli występuje podczas deserializacji, głuptasku.
Erti-Chris Eelmaa
24
TypeNameHandling.Autozrobi to również i jest ładniejszy, ponieważ nie zapisuje nazwy typu instancji, gdy pasuje ona do typu pola / właściwości, co często ma miejsce w przypadku większości pól / właściwości.
Roman Starkov
2
To nie działa, gdy deserializacja jest wykonywana w innym rozwiązaniu / projekcie. Podczas serializacji nazwa rozwiązania jest osadzana w typie: „NAZWA ROZWIĄZANIA.Modele.Model”. Podczas deserializacji w innym rozwiązaniu zgłosi „JsonSerializationException: nie można załadować zestawu„ SOLUTIONNAME ”.
Smutny programista CRUD
19

Ponieważ pytanie jest tak popularne, warto dodać, co zrobić, jeśli chcesz kontrolować nazwę właściwości typu i jej wartość.

Długą drogą jest napisanie niestandardowych JsonConverters do obsługi (de) serializacji przez ręczne sprawdzenie i ustawienie właściwości type.

Prostszym sposobem jest użycie JsonSubTypes , który obsługuje wszystkie standardowe elementy za pośrednictwem atrybutów:

[JsonConverter(typeof(JsonSubtypes), "Sound")]
[JsonSubtypes.KnownSubType(typeof(Dog), "Bark")]
[JsonSubtypes.KnownSubType(typeof(Cat), "Meow")]
public class Animal
{
    public virtual string Sound { get; }
    public string Color { get; set; }
}

public class Dog : Animal
{
    public override string Sound { get; } = "Bark";
    public string Breed { get; set; }
}

public class Cat : Animal
{
    public override string Sound { get; } = "Meow";
    public bool Declawed { get; set; }
}
rzippo
źródło
4
Rozumiem, że tak jest, ale nie jestem fanem konieczności informowania klasy bazowej o wszystkich elementach „KnownSubType” ...
Matt Knowles
2
Jeśli spojrzysz na dokumentację, są inne opcje. Podałem tylko przykład, który lubię bardziej.
rzippo
1
Jest to bezpieczniejsze podejście, które nie uwidacznia usługi do ładowania dowolnych typów podczas deserializacji.
David Burg
3

Użyj tego JsonKnownTypes , jest to bardzo podobny sposób użycia, po prostu dodaj dyskryminator do json:

[JsonConverter(typeof(JsonKnownTypeConverter<BaseClass>))]
[JsonKnownType(typeof(Base), "base")]
[JsonKnownType(typeof(Derived), "derived")]
public class Base
{
    public string Name;
}
public class Derived : Base
{
    public string Something;
}

Teraz podczas serializacji obiektu w json będą dodawać "$type"z "base"oraz "derived"wartości i będzie użyty do deserializowania

Przykład listy zserializowanej:

[
    {"Name":"some name", "$type":"base"},
    {"Name":"some name", "Something":"something", "$type":"derived"}
]
Dmitry
źródło