1. Giriş
Sadə obyektlərin serialization-ı məktub göndərmək kimidir: hər şey sadədir, heç nə itmir. Amma tez-tez obyektlərimiz "ailə albomları" olur: onların içində daxili obyektlər, kolleksiyalar və hətta kolleksiyaların kolleksiyaları olur.
Təsəvvür edin ki, bizim istifadəçi sadəcə Person adı və yaşı olan obyekt deyil, məsələn, onun bir sıra kontaktları (Contact), bir neçə ev/iş ünvanı və bəlkə də List<Pet> tipində sahəsi var, əgər insan heyvanları sevirsə. Bütün bunlar — iç-içə obyektlər və kolleksiyalardır.
.NET serializer-ləri bununla necə davranır? Kompleks obyekt ağaclarını serialize edərkən hansı nüansları yadda saxlamaq lazımdır? Kolleksiyaların içində başqa kolleksiyalar olsa hər şey sıradan çıxar? Bugün biz nələri gözləmək lazım olduğunu və çətin hallarda nə etmək lazım olduğunu öyrənəcəyik.
“Daxili olma” .NET terminologiyasında nə deməkdir
Daxili obyekt — əsas obyektinizin içində property və ya field kimi olan başqa bir obyekt deməkdir. Məsələn, istifadəçinin genişləndirilmiş modelinə baxaq:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public List<Contact> Contacts { get; set; } // Daxili obyektlərin kolleksiyası
public Address? HomeAddress { get; set; } // Bir daxili obyekt (nullable)
}
public class Contact
{
public string Type { get; set; } // Məsələn, Email və ya Phone
public string Value { get; set; }
}
public class Address
{
public string City { get; set; }
public string Street { get; set; }
}
Diqqət yetirin — insanın bir neçə kontakta malik ola bilər, amma ünvan yalnız biri ola bilər (və o null ola bilər). Bu bir çox modellərin klassik nümunəsidir.
2. System.Text.Json ilə obyektlərin və kolleksiyaların serialization-ı
Sadə nümunə: kolleksiyanı serialize və deserialize edək
Gəlin real həyata baxaq:
using System.Text.Json;
var person = new Person
{
Name = "Anna",
Age = 28,
Contacts = new List<Contact>
{
new Contact { Type = "Email", Value = "anna@example.com" },
new Contact { Type = "Phone", Value = "+1234567890" }
},
HomeAddress = new Address { City = "Berlin", Street = "Alexanderplatz, 1" }
};
string json = JsonSerializer.Serialize(person, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(json);
Nəticə təxminən belə olacaq:
{
"Name": "Anna",
"Age": 28,
"Contacts": [
{
"Type": "Email",
"Value": "anna@example.com"
},
{
"Type": "Phone",
"Value": "+1234567890"
}
],
"HomeAddress": {
"City": "Berlin",
"Street": "Alexanderplatz, 1"
}
}
Serializer daxili quruluşu yaxşı başa düşür. Əgər property başqa obyektdirsə, onu "daxili" olaraq serialize edir. Əgər siyahıdırsa — array kimi serialize olunur.
Fakt: Belə serialization istənilən kolleksiyalar və daxili obyektlər üçün avtomatik işləyir, əgər onlar public və public getter/setter-ə malikdirsə (get/set).
Deserialization: "qutudan çıxarılmış" halda işləyir
string jsonInput = /* Bizim yeni aldığımız JSON */;
Person deserializedPerson = JsonSerializer.Deserialize<Person>(jsonInput);
Console.WriteLine(deserializedPerson.Name); // "Anna"
Console.WriteLine(deserializedPerson.Contacts[0].Type); // "Email"
Hər şey işləyir — kontaktlar JSON array-dən geri List<Contact>-ə çevrilir, HomeAddress — geri Address obyektinə.
3. Kolleksiyalar necə serialize olunur: List, massivlər, Dictionary
Modelinizin içində tez-tez yalnız siyahılar (List<T>) yox, həmçinin sözlüklər (Dictionary<TKey, TValue>) və çoxölçülü massivlər də olur.
Massiv nümunəsi
public class Team
{
public string Name { get; set; }
public Person[] Members { get; set; }
}
var team = new Team
{
Name = "Developers",
Members = new[]
{
new Person { Name = "Aleksey", Age = 31 },
new Person { Name = "Ekaterina", Age = 27 }
}
};
string json = JsonSerializer.Serialize(team, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(json);
JSON:
{
"Name": "Developers",
"Members": [
{
"Name": "Aleksey",
"Age": 31,
"Contacts": null,
"HomeAddress": null
},
{
"Name": "Ekaterina",
"Age": 27,
"Contacts": null,
"HomeAddress": null
}
]
}
Kolleksiyanın serialization-ı eyni tipdə bir çox məktubun qablaşdırılması kimidir: hər element ayrı "kart" olur.
Dictionary ilə nümunə
public class Phonebook
{
public Dictionary<string, string> Phones { get; set; }
}
var phonebook = new Phonebook
{
Phones = new Dictionary<string, string>
{
{ "Andrey", "+12998887766" },
{ "Maria", "+12882223344" }
}
};
string json = JsonSerializer.Serialize(phonebook, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(json);
JSON:
{
"Phones": {
"Andrey": "+12998887766",
"Maria": "+12882223344"
}
}
JSON-də sözlük dinamik açarlı obyektə çevrilir.
4. Daxili kolleksiyalar (List içində List, massivlərin massivləri)
.NET belə "matryoshkaları" serializasiya etməyi də bilir:
public class Zoo
{
public List<List<Animal>> AnimalGroups { get; set; }
}
public class Animal { public string Name { get; set; } }
var zoo = new Zoo
{
AnimalGroups = new List<List<Animal>>
{
new List<Animal> { new Animal { Name = "Lev" }, new Animal { Name = "Tigr" } },
new List<Animal> { new Animal { Name = "Medved" } }
}
};
string json = JsonSerializer.Serialize(zoo, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(json);
JSON:
{
"AnimalGroups": [
[ { "Name": "Lev" }, { "Name": "Tigr" } ],
[ { "Name": "Medved" } ]
]
}
Deserialization mərhələsində də eyni qayda ilə geri alınır.
5. XmlSerializer-də daxili obyektlərin serialization xüsusiyyətləri
Daxili obyektin serialization nümunəsi
using System.Xml.Serialization;
using System.IO;
var person = new Person
{
Name = "Ivan",
Age = 35,
HomeAddress = new Address { City = "Bonn", Street = "Beethoven str., 100" },
Contacts = new List<Contact>
{
new Contact { Type = "Email", Value = "ivan@domain.de" }
}
};
var serializer = new XmlSerializer(typeof(Person));
using var writer = new StringWriter();
serializer.Serialize(writer, person);
Console.WriteLine(writer.ToString());
Nəticə:
<?xml version="1.0" encoding="utf-16"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Ivan</Name>
<Age>35</Age>
<Contacts>
<Contact>
<Type>Email</Type>
<Value>ivan@domain.de</Value>
</Contact>
</Contacts>
<HomeAddress>
<City>Bonn</City>
<Street>Beethoven str., 100</Street>
</HomeAddress>
</Person>
XmlSerializer-də kolleksiyaların xüsusiyyətləri
Default olaraq XmlSerializer kolleksiyanı "Contacts" teqi kimi serialize edir və hər element üçün ayrıca "Contact" teqi yaradır. Bunun üçün kolleksiya tipi public olmalı və onun elementləri serializable tiplər olmalıdır.
Mühüm məqam. Əgər kolleksiya boşdursa, XML hələ də boş teqi daxil edəcək:
<Contacts />
Dəstək olunan kolleksiyalar. XmlSerializer List<T>, massivlər T[], ICollection<T>-ni implement edən kolleksiyaları dəstəkləyir. Məsələn, Dictionary<TKey, TValue>-ni birbaşa dəstəkləmir! Sözlüklərin serialization-ı üçün əlavə texnikalar və ya üçüncü tərəf kitabxanalarından istifadə etmək lazım gəlir.
6. Newtonsoft.Json-də obyektlər və kolleksiyaların dəstəyi
Newtonsoft.Json daxili struktur dəstəkləməsində bir az daha çevikdir. 99% halda hər şey System.Text.Json kimi işləyir, amma üstünlükləri var: private field-ları serializasiya etmək (əgər açıqca göstərsəniz), daha mürəkkəb açarlı sözlüklər, dynamic tiplər və hətta dövrü referensiyalar (xüsusi ayarlarla) serializasiya etmək mümkündür.
Nümunə
using Newtonsoft.Json;
var person = new Person
{
Name = "Pavel",
Age = 40,
Contacts = new List<Contact>
{
new Contact { Type = "SMS", Value = "+10000000013" }
}
};
string json = JsonConvert.SerializeObject(person, Formatting.Indented);
Console.WriteLine(json);
Fərq. Məsələn, obyektin private field-ları varsa, onları ContractResolver ilə serialize etmək olar. Amma əsas daxili kolleksiyalar və obyektlər üçün hər şey "qutudan çıxarılmış" halda avtomatik işləyir.
7. Tipik səhvlər, tələ-fəlakətlər və nüanslar
Səhv 1: Daxili obyektlər — null.
Əgər serialize etdiyiniz obyektin bəzi property-ləri doldurulmayıb (null), JSON və ya XML-də onlar ola da bilər, olmaya da bilər, və ya null kimi yazıla bilər. Məsələn, əgər HomeAddress Person-da null-dırsa, JSON-da:
"HomeAddress": null
XML-də default olaraq belə bir teq olmayacaq — bunu serializer ayarlarına əlavə etməklə daxil etmək olar ([XmlElement(IsNullable=true)]).
Səhv 2: Private property/field-ların serialization-ı.
Default olaraq əksər serializer-lər (həm JSON, həm XML) yalnız public property-lərlə işləyir. Əgər private və protected field-ları serialize etmək istəyirsinizsə, onları public elan etməli və ya serializer-i konfiqurasiya etməlisiniz (məsələn, ContractResolver vasitəsilə Newtonsoft.Json-də).
Səhv 3: Default konstruktoru olmayan siniflər.
XML-serialization-da (və çox vaxt JSON-da) sinifin parametri olmayan public konstruktoru olmalıdır. Əgər belə konstruktor yoxdursa — deserialization zamanı exception alınacaq.
Səhv 4: Sözlüklərin XML-də serialization-ı.
XmlSerializer birbaşa Dictionary<TKey, TValue>-ni dəstəkləmir. Bu tez-tez gözlənilməz olur. Həll yolu kimi sözlüyü cütlüklər siyahısına bükmək və ya digər serializer-lərdən istifadə etmək olar.
Səhv 5: Dövrü referensiyalar.
Əgər bir obyekt başqa obyektə referensiya edir və həmin obyekt yenidən birinciyə referensiya edirsə (məsələn, parent-child əlaqəsi), serialization "dairəvi" ola bilər və bu StackOverflowException və ya dövrü referensiya ilə bağlı başqa exception-la nəticələnə bilər. JSON serializer-lərdə bunu ayarlarla həll etmək mümkündür, amma çox vaxt daha düzgün olan arxitekturanı nəzərdən keçirməkdir.
GO TO FULL VERSION