Json.net membuat serial / deserialize tipe turunan?

99

json.net (newtonsoft)
Saya melihat-lihat dokumentasi tetapi saya tidak dapat menemukan apa pun tentang ini atau cara terbaik untuk melakukannya.

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

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

Sekarang saya memiliki objek Berasal dalam daftar serial. Bagaimana cara menghapus nama daftar dan mendapatkan kembali jenis turunan?

Akan
sumber
Itu bukanlah cara kerja warisan. Anda dapat menentukan JsonConvert.Deserialize <Derived> (teks); untuk menyertakan bidang Nama. Karena Derived IS A Base (bukan sebaliknya), Base tidak tahu apa-apa tentang definisi Derived.
M.Babcock
Maaf, diklarifikasi sedikit. Masalahnya adalah saya memiliki daftar yang berisi objek dasar dan turunan. Jadi saya perlu mencari tahu bagaimana saya memberitahu newtonsoft bagaimana cara deserialize item turunan.
Akankah
Saya apakah Anda menyelesaikan ini. Saya memiliki masalah yang sama
Luis Carlos Chavarría

Jawaban:

46

Jika Anda menyimpan tipe di Anda text(sebagaimana seharusnya dalam skenario ini), Anda dapat menggunakan JsonSerializerSettings.

Lihat: cara deserialisasi JSON menjadi <BaseType> IEnumerable dengan Newtonsoft JSON.NET

Berhati-hatilah. Menggunakan apa pun selain TypeNameHandling = TypeNameHandling.Nonedapat membuka diri Anda pada kerentanan keamanan .

kamranicus
sumber
24
Anda juga dapat menggunakan TypeNameHandling = TypeNameHandling.Auto- ini akan menambahkan $typeproperti HANYA untuk instance di mana jenis yang dideklarasikan (yaitu Base) tidak cocok dengan jenis instance (yaitu Derived). Dengan cara ini, JSON Anda tidak terlalu membengkak TypeNameHandling.All.
AJ Richardson
Saya terus menerima Jenis penyelesaian kesalahan yang ditentukan dalam JSON '..., ...'. Jalur '$ type', baris 1, posisi 82. Ada ide?
suap
3
Hati-hati saat menggunakan ini pada titik akhir publik karena membuka masalah keamanan: alphabot.com/security/blog/2017/net/…
gjvdkamp
1
@gjvdkamp JEEZ terima kasih untuk ini, saya tidak tahu tentang ini. Akan menambah posting saya.
kamranicus
96

Anda harus mengaktifkan Type Name Handling dan meneruskannya ke (de) serializer sebagai parameter pengaturan.

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);

Ini akan menghasilkan deserialisasi kelas turunan yang benar. Kekurangannya adalah itu akan memberi nama semua objek yang Anda gunakan, karena itu akan memberi nama daftar tempat Anda meletakkan objek.

Madmenyo
sumber
31
+1. Saya googling selama 30 menit sampai saya benar-benar menemukan bahwa Anda perlu menggunakan pengaturan yang sama untuk SerializeObject & DeserializeObject. Saya berasumsi itu akan menggunakan $ type secara implisit jika itu ada saat deserializing, konyol saya.
Erti-Chris Eelmaa
24
TypeNameHandling.Autoakan melakukannya juga, dan lebih bagus karena tidak menulis nama tipe instance ketika cocok dengan tipe bidang / properti, yang sering terjadi pada sebagian besar bidang / properti.
Roman Starkov
2
Ini tidak berfungsi saat deserialisasi dilakukan pada solusi / proyek lain. Pada serialisasi, nama Solusi tertanam di dalamnya sebagai jenis: "SOLUTIONNAME.Models.Model". Saat deserialisasi pada solusi lain, ia akan menampilkan "JsonSerializationException: Tidak dapat memuat assembly 'SOLUTIONNAME'.
Sad CRUD Developer
19

Karena pertanyaan ini sangat populer, mungkin berguna untuk menambahkan apa yang harus dilakukan jika Anda ingin mengontrol nama properti tipe dan nilainya.

Cara yang panjang adalah menulis custom JsonConverters untuk menangani (de) serialisasi dengan secara manual memeriksa dan mengatur properti type.

Cara yang lebih sederhana adalah dengan menggunakan JsonSubTypes , yang menangani semua boilerplate melalui atribut:

[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.dll
sumber
4
Saya mendapatkan kebutuhan tersebut, tetapi saya bukan penggemar karena harus membuat kelas dasar menyadari semua "SubTipe yang Diketahui" ...
Matt Knowles
2
Ada opsi lain jika Anda melihat dokumentasinya. Saya hanya memberikan contoh yang saya lebih suka.
rzippo
1
Ini adalah pendekatan yang lebih aman yang tidak mengekspos layanan Anda untuk memuat jenis arbitrer setelah de-serialisasi.
David Burg
3

Gunakan JsonKnownTypes ini , cara penggunaannya sangat mirip, hanya menambahkan diskriminator ke 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;
}

Sekarang ketika Anda cerita bersambung objek dalam json akan menambah "$type"dengan "base"dan "derived"nilai dan akan digunakan untuk deserialize

Contoh daftar berseri:

[
    {"Name":"some name", "$type":"base"},
    {"Name":"some name", "Something":"something", "$type":"derived"}
]
Dmitry
sumber