1. Giriş
Bu dərsdə biz "mənə sadəcə siyahını saxla" səviyyəsindən çıxıb System.Text.Json ilə elastik, dəqiq və performanslı serialization-a doğru addım atacağıq. Müasir .NET layihələrində JSON — məlumat mübadiləsinin de-facto standartıdır. Əsas çağırışlar Serialize/Deserialize sadədir, amma real tapşırıqlar incə tənzimləmə tələb edir: sahələrin ignorlanması/yenidən adlandırılması, tarix formatlarının idarəsi, dövrlərə qarşı qorunma, böyük həcmli məlumatlarla iş, öz konverterlər və s.
Biz yalnız JsonSerializer metodlarını yox, həm də JsonSerializerOptions vasitəsilə parametrləri, atributları, Stream-lə işi, yaddaşın idarəsini və serialization qaydalarını özəl JsonConverter vasitəsilə necə vurğuladığını görəcəyik.
System.Text.Json-un qısa tarixi və mövqeyi
Uzun müddət .NET-də Newtonsoft.Json (Json.NET) hökmran idi — çevik və yetkin, amma həmişə ən sürətli və asılılıqları ən az olan deyildi. .NET Core 3.0 ilə birlikdə daxili System.Text.Json gəldi: yüksək performans, minimal asılılıqlar (platformaya daxil), ASP.NET Core ilə sıx inteqrasiya və .NET versiyaları ilə davamlı inkişaf.
2. Əsas siniflər və metodlar
Əsas element — statik sinif JsonSerializer, hansı ki iki istiqamət verir:
- Serialization: obyekt → JSON-sətir (Serialize)
- Deserialization: JSON-sətir → lazımi tipdə obyekt (Deserialize)
Sadə obyektin serialization nümunəsi
using System.Text.Json;
var person = new Person { Name = "İvan", Age = 30 };
string jsonString = JsonSerializer.Serialize(person);
Console.WriteLine(jsonString); // {"Name":"İvan","Age":30}
Deserialization nümunəsi
var json = "{\"Name\":\"Anna\",\"Age\":22}";
var anna = JsonSerializer.Deserialize<Person>(json);
Console.WriteLine(anna.Name); // Anna
Qeyd: Person tipi əvvəlki mühazirələrdə artıq implementasiya olunub — burada onu istifadə edirik.
3. Serialization-in idarəsi: JsonSerializerOptions
Real layihələrdə demək olar həmişə tənzimləmələr lazımdır: propertilərin adları camelCase, tarix formatları, dövrlərin emalı, default dəyərlər və s. Bütün bunlar JsonSerializerOptions vasitəsilə tənzimlənir.
Nümunə tənzimləmə
var options = new JsonSerializerOptions
{
WriteIndented = true, // JSON-ı gözəl formatlamaq (boşluq və sətir fasilələri əlavə edir)
PropertyNameCaseInsensitive = true, // Deserialization zamanı propertilərin registrini nəzərə almamaq
PropertyNamingPolicy = JsonNamingPolicy.CamelCase // propertilər üçün camelCase (PascalCase əvəzinə)
};
string json = JsonSerializer.Serialize(person, options);
/*
{
"name": "İvan",
"age": 30
}
*/
Niyə bu vacibdir? Çoxlu frontend framework-lər məhz camelCase-i gözləyir, .NET-in standartı olan PascalCase yox.
4. Atributlar: System.Text.Json.Serialization
Bəzən serialization-u modelin üzərindən atributlarla idarə etmək daha rahatdır. Atributlar sahələrə/properties-ə əlavə olunur ki, adlara, daxil edilməyə/çıxarılmağa və dəyərin emalına təsir etsin.
Əsas atributlar
| Atribut | Necə işləyir |
|---|---|
|
Property-ni serialization/deserialization-dan çıxarır |
|
JSON-da başqa ad istifadə edir |
|
Non-public property/field-i serialization-a daxil edir |
|
Rəqəmlərin emalını idarə edir |
Nümunə: atributlarla property idarəsi
using System.Text.Json.Serialization;
public class Person
{
[JsonPropertyName("full_name")]
public string Name { get; set; }
[JsonIgnore]
public int SecretCode { get; set; }
public int Age { get; set; }
}
var person = new Person { Name = "Pyotr", Age = 45, SecretCode = 123 };
string json = JsonSerializer.Serialize(person);
// {"full_name":"Pyotr","Age":45}
Nəzərə alın: SecretCode JSON-a düşmədi, Name isə "full_name" kimi serialize olundu.
5. Kolleksiyaların və daxili obyektlərin serialization-u
Kolleksiyalar — sadədir
var numbers = new List<int> { 1, 2, 3 };
string json = JsonSerializer.Serialize(numbers); // [1,2,3]
var people = new List<Person> {
new Person { Name = "Anna", Age = 20 },
new Person { Name = "Maksim", Age = 40 }
};
string jsonList = JsonSerializer.Serialize(people);
// [{"Name":"Anna","Age":20},{"Name":"Maksim","Age":40}]
Daxili strukturlar
public class Group
{
public string Name { get; set; }
public List<Person> Members { get; set; }
}
var group = new Group
{
Name = "İnkişaf etdiricilər",
Members = new List<Person>
{
new Person { Name = "Sasha", Age = 23 },
new Person { Name = "Masha", Age = 28 }
}
};
string jsonGroup = JsonSerializer.Serialize(group, options);
/*
{
"name": "İnkişaf etdiricilər",
"members": [
{ "name": "Sasha", "age": 23 },
{ "name": "Masha", "age": 28 }
]
}
*/
6. Deserialization: nəyə diqqət etmək lazımdır?
var json = "[{\"Name\":\"İvan\",\"Age\":21}]";
var list = JsonSerializer.Deserialize<List<Person>>(json);
Console.WriteLine(list[0].Name); // İvan
Tez-tez rast gəlinən ssenari: əgər JSON-da hansısa sahə yoxdursa, uyğun property default dəyər alacaq. JSON-da modeldə olmayan əlavə sahələr yox sayılır. Amma tiplər uyğun gəlməzsə (məsələn, sayı gözləyəndə string gəlsə) — deserialization zamanı exception atılacaq.
7. Tarixlərin, zamanın, formatların və ədədi dəyərlərin emalı
public class Meeting
{
public string Topic { get; set; }
public DateTime Time { get; set; }
}
var meeting = new Meeting { Topic = "Toplantı", Time = DateTime.Now };
string json = JsonSerializer.Serialize(meeting);
// {"Topic":"Toplantı","Time":"2024-06-06T20:30:00.0000000+03:00"}
Default olaraq DateTime ISO 8601 formatında serialize olunur. Başqa görünüş (məsələn, yalnız tarix) lazımdırsa, ayrı property və ya custom converter istifadə edin (aşağıya bax).
FAQ: Ədədlərin string kimi serialize olunmasını (məsələn, telefon nömrələri və ya böyük ID-lər) istəyirsinizsə, [JsonNumberHandling(JsonNumberHandling.WriteAsString)] atributundan istifadə edin.
8. Stream-lər və fayllarla iş
İş yalnız sətirlərlə deyil, həm də Stream-lərlə aparıla bilər — bu böyük məlumatlar (fayllar, şəbəkə) üçün vacibdir.
Fayla yazmaq nümunəsi
using var fs = File.Create("person.json");
JsonSerializer.Serialize(fs, person);
// Unutmayın fs.Flush() çağırmaq və ya using istifadə etmək!
Fayldan oxuma nümunəsi
using var fs = File.OpenRead("person.json");
var restored = JsonSerializer.Deserialize<Person>(fs);
Stream-lərlə asinxron metodlar da mövcuddur: SerializeAsync/DeserializeAsync — yüksək yüklü servislər üçün faydalıdır.
9. Custom konverterlər
Əgər standart qaydalar uyğun gəlmirsə (qeyri-standart tarix/ədəd formatları, mürəkkəb dəyərlər, öz strukturlar) — JsonConverter yazırsınız.
Nümunə: tarix yalnız "dd.MM.yyyy" kimi
public class CustomDateConverter : JsonConverter<DateTime>
{
public override DateTime Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return DateTime.ParseExact(reader.GetString(), "dd.MM.yyyy", null);
}
public override void Write(
Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString("dd.MM.yyyy"));
}
}
var options = new JsonSerializerOptions();
options.Converters.Add(new CustomDateConverter());
var dt = new DateTime(2024, 6, 1);
string json = JsonSerializer.Serialize(dt, options); // "01.06.2024"
Custom konverterlər koordinatlar, vektorlar, rənglər, qeyri-standart tarixlər və valyutalar, müxtəlif ID-formatlar və s. üçün faydalıdır.
10. Faydalı nüanslar
Dövri istinadların və dərin iyerarxiyaların emalı
var options = new JsonSerializerOptions
{
ReferenceHandler = ReferenceHandler.Preserve, // Bütün obyektləri $id/$ref ilə saxlayır
WriteIndented = true
};
Vacib: JSON-a xidmətçi property-lər $id və $ref əlavə olunacaq. Xarici sistemlərlə mübadilədə onlar başa düşülməyə bilər.
System.Text.Json və Newtonsoft.Json arasındakı fərqlər
System.Text.Json artıq kifayət qədər güclüdür, amma hələ Newtonsoft.Json-un bəzi ssenarilərini tam əhatə etmir (məsələn, private konstrukturlar, mürəkkəb dynamic obyektlər və s.). Əksər standart tapşırıqlar üçün daxili serializer-i tövsiyə edirəm — o daha sürətlidir və əlavə asılılıqlara ehtiyac yoxdur.
JSON-la interaktiv iş: DOM API
Tam model olmadan JSON üzərində gəzmək lazım gəldikdə, JsonDocument və JsonElement-dən istifadə edin.
using var doc = JsonDocument.Parse(jsonString);
JsonElement root = doc.RootElement;
if (root.TryGetProperty("Name", out var nameProperty))
{
Console.WriteLine(nameProperty.GetString());
}
11. Faydalı opsiyalar və onların effektləri
| Property | Dəyər/Təyinat |
|---|---|
|
true — indent-lə formatlama |
|
true — deserialization zamanı properti adlarının registrini nəzərə almamaq |
|
|
|
null/dəyərlər üçün ignore qaydaları |
|
, |
|
true — array-ın sonunda vergülü icazə ver |
|
Ədədlərin string-ə/əksinə çevrilməsi (və s.) |
|
Custom konverterlərin siyahısı |
12. Tez-tez rastlanan səhvlər və praktik məsləhətlər
Səhv №1: deserialization zamanı yanlış tip. Əgər siyahını serialize etmisinizsə, mütləq siyahıya deserialize edin: List<T>, tək obyektə yox.
Səhv №2: properti adlarının registi ilə bağlı problem. Registrə həssaslıq olmadan propertilər "tapılmaya" bilər. PropertyNameCaseInsensitive-dən istifadə edin və ya PropertyNamingPolicy-u tənzimləyin.
Səhv №3: tarixlərin düzgün emal olunmaması. Default format ISO 8601-dir. Başqa format lazımdırsa — JsonConverter<DateTime> yaradın və tətbiq edin.
Səhv №4: private/static sahələrin serialize olunacağını gözləmək. Default olaraq public propertilər götürülür. Nadir hallarda atributlar (məsələn, [JsonInclude]) istifadə edin.
Səhv №5: default dəyərlərin anlaşılmaması. JSON-da olmayan sahə → property üçün default dəyər. Bu məntiqdə nəzərə alınmalıdır.
Səhv №6: stream-lərin səhv idarə olunması. Resursları using və ya await using ilə bağlayın ki, leak və bloklanma olmasın.
GO TO FULL VERSION