CodeGym /Kurslar /C# SELF /Daxili və ierarxik obyektlərin serialization-u

Daxili və ierarxik obyektlərin serialization-u

C# SELF
Səviyyə, Dərs
Mövcuddur

1. Giriş

Təsəvvür edin kitab mağazası üçün bir tətbiq: obyektlər yalnız kitablar deyil, həmçinin müəlliflər (onların bio-su var), nəşriyyatlar kitablar çıxarır, əməkdaşlar, bölmələr... Əgər serialization yalnız "düz" obyektləri dəstəkləsəydi, tətbiqimiz sadəcə qeyd dəftəri səviyyəsində qalardı. Real layihələrdə məlumatlar demək olar həmişə çoxsəviyyəlidir və daxildir. Buna görə ierarxik strukturların serialize və deserialize edilməsini bacarmaq — ortalama proqramçını həqiqi .NET serialization ustasından ayıran bacarıqdır.

Biz göstərəcəyik ki, müasir C# serializer-ləri (nümunə üçün System.Text.Json) yalnız obyekt ağaclarını deyil, tam "cəngəllikləri" də saxlamağa imkan verir. Həmçinin necə düzgün siniflər qurmaq lazımdır ki, sonra məlumatları əllə parça-parça çıxarmağa ehtiyac qalmasın.

2. İerarxik strukturların modelləşdirilməsi

Gəlin modelimizi genişləndirək. Əvvəlki nümunələrdə Book, Author və Library sinifləri var idi, Library kitab siyahısını saxlayırdı. İndi bir səviyyə əlavə edək: hər müəllifin yazdığı kitabların siyahısı olsun (bəli, məlumatlar təkrarlana bilər — bunun nəyə gətirib çıxara biləcəyini sonra müzakirə edəcəyik!), və kitabxanaya Employee siyahısı əlavə edək.

Sinif sxemi

Obyekt strukturu təxminən belə görünəcək:

classDiagram
    class Library {
        List~Book~ Books
        List~Employee~ Employees
        string Name
    }
    class Book {
        string Title
        Author Author
        int Year
    }
    class Author {
        string Name
        int BirthYear
        List~Book~ Books
    }
    class Employee {
        string Name
        string Position
    }

    Library "1" -- "many" Book
    Library "1" -- "many" Employee
    Book "1" -- "1" Author
    Author "1" -- "many" Book

Bu yanaşma tam kitabxana sistemini həyata keçirməyə imkan verir. Bəli, "dairələnmiş" referansların yaranma riski var (məsələn, müəllifin kitabları var, kitabın isə müəllifi var: sonsuz serialization dövrəsi!). Buna daha sonra toxunacağıq.

3. Siniflərin implementasiyası nümunəsi

İlk öncə C# siniflərini təsvir edək. Şərhlər də əlavə edək:

using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;

public class Library
{
    public string Name { get; set; }
    public List<Book> Books { get; set; } = new();
    public List<Employee> Employees { get; set; } = new();
}

public class Book
{
    public string Title { get; set; }
    public int Year { get; set; }

    public Author Author { get; set; }
}

public class Author
{
    public string Name { get; set; }
    public int BirthYear { get; set; }
    
    // VAJIB! Bu sahə potensial olaraq "təhlükəlidir": dövrə yarada bilər.
    // Amma nümayiş üçün onu daxil edirik.
    public List<Book> Books { get; set; } = new();
}

public class Employee
{
    public string Name { get; set; }
    public string Position { get; set; }
}

4. Struktur yığımı: kitabxananı yaradırıq

İndi kitabxananı, kitabları, müəllifləri və əməkdaşları yaradıb serialization-u yoxlayaq.

// Müəlliflər yaradırıq
var author1 = new Author { Name = "Vilyam Goldinq", BirthYear = 1911 };
var author2 = new Author { Name = "Con Updike", BirthYear = 1932 };
var author3 = new Author { Name = "Ceyrom Selingcer", BirthYear = 1919 };

// Kitablar yaradırıq
var book1 = new Book { Title = "Povelitel muh", Year = 1954, Author = author1 };
var book2 = new Book { Title = "Kentavr", Year = 1963, Author = author2 };
var book3 = new Book { Title = "Nad propastyu vo rzhi", Year = 1951, Author = author3 };

// Kitabları müəlliflərə əlavə edirik
author1.Books.Add(book1);
author2.Books.Add(book2);
author3.Books.Add(book3);

// Əməkdaşlar yaradırıq
var emp1 = new Employee { Name = "Ivan Ivanov", Position = "Bibliotekar" };
var emp2 = new Employee { Name = "Sergey Sergeev", Position = "Direktor" };

// Kitabxananı tərtib edirik
var library = new Library
{
    Name = "Şəhər kitabxanası",
    Books = new List<Book> { book1, book2, book3 },
    Employees = new List<Employee> { emp1, emp2 }
};

Təbii ki, real kodda obyektlərin və əlaqələrin yaradılmasını avtomatlaşdırardınız ki, hər kitabı müəllifə əllə bağlamayasınız. Amma nümunəmiz üçün bu kifayətdir.

5. JSON-a serialization

Classik yanaşmadan istifadə edək — System.Text.Json vasitəsilə:

using System.Text.Json;

// Kitabxananı JSON-a serialize edirik
var options = new JsonSerializerOptions
{
    WriteIndented = true, // Gözəl format, indentlə
    ReferenceHandler = ReferenceHandler.IgnoreCycles // Dövrələnməni qarşısını alırıq!
};

string json = JsonSerializer.Serialize(library, options);

// Nəticəni ekrana veririk
Console.WriteLine(json);

Maraqlı məqam: Əgər ReferenceHandler.IgnoreCycles seçimini istifadə etməsəniz, serialization daimi dövrəyə girə bilər — müəllifin kitabları, kitabın müəllifi olduğundan serializer sonsuz "yüzəcək" və nəticədə exception atacaq. IgnoreCycles problemi həll edir: strukturda eyni obyektə yenidən rast gəldikdə serializer təkrar serialize etmək əvəzinə null yazır.

JSON necə görünəcək?

{
  "Name": "Şəhər kitabxanası",
  "Books": [
    {
      "Title": "Kentavr",
      "Year": 1963,
      "Author": {
        "Name": "Con Updike",
        "BirthYear": 1932,
        "Books": [
          {
            "Title": "Kentavr",
            "Year": 1963,
            "Author": null
          },
          {
            "Title": "Istvikskie vedmy",
            "Year": 1984,
            "Author": null
          }
        ]
      }
    },
    {
      "Title": "Istvikskie vedmy",
      "Year": 1984,
      "Author": {
        "Name": "Con Updike",
        "BirthYear": 1932,
        "Books": [
          {
            "Title": "Kentavr",
            "Year": 1963,
            "Author": null
          },
          {
            "Title": "Istvikskie vedmy",
            "Year": 1984,
            "Author": null
          }
        ]
      }
    }
  ],
  "Employees": [
    {
      "Name": "Ivan Ivanov",
      "Position": "Bibliotekar"
    },
    {
      "Name": "Sergey Sergeev",
      "Position": "Direktor"
    }
  ]
}

Diqqət edin: Books massivi daxilində müəllifin kitablarında artıq müəllif haqqında məlumat yoxdur — əvəzinə Author: null. Bu dövrəni serialization zamanı kəsmək üsuludur.

6. JSON-dan obyektlərə geri deserialize

İndi məlumatı yenidən obyektlərə qaytaraq:

// JSON-dan obyektə bərpa edirik
var libraryCopy = JsonSerializer.Deserialize<Library>(json, options);

Console.WriteLine(libraryCopy.Name); // "Şəhər kitabxanası"
Console.WriteLine($"Kitablar: {libraryCopy.Books.Count}");
Console.WriteLine($"Əməkdaşlar: {libraryCopy.Employees.Count}");

Amma! Dövrəvi referansların bərpası artıq 100% işləməyə bilər: müəllifin siyahısındakı daxil edilmiş kitablarda Author sahəsi null olacaq, çünki serializer sonsuzluqdan qorunmaq üçün zənciri kəsdi.

Vacib məqam: Məsələn, valideyn — uşaqlar — valideyn kimi mürəkkəb qarşılıqlı əlaqələri standart serializer vasitəsilə düzgün saxlamaq həmişə kompromis tələb edir: ya bəzi əlaqələr itir, ya da deserializasiya sonrası əlaqələri əl ilə bərpa etmək lazım gəlir.

7. Dövrəvi referanslar (daxililik vs dövrlər)

Əgər strukturunuzda obyektlər digər obyektlər vasitəsilə özünə istinad edirsə (dövrəvi referans) — standart serializer-lər, məsələn System.Text.Json və Newtonsoft.Json, fərqli davranırlar. ReferenceHandler.IgnoreCycles seçimi yaranana qədər serialization "ReferenceLoopHandling detected" tipli exception ilə dayandırılırdı. İndi isə təkrar referans əvəzinə sadəcə null qoyulur.

Buradakı tutqac nədir?

Müsbət: kodunuz exception atmır və proqram çökmür.

Mənfi: deserializasiya sonrası bəzi bağlantıları əl ilə bərpa etmək lazım gəlir. Məsələn, istifadəçilər qrafı obyektlərin bir-birinə istinad etdiyi halda (məsələn, əməkdaş və onun müdiri) — bərpa etdikdən sonra bəzi referanslar boş qala bilər.

8. Serialization üçün mürəkkəb strukturlar necə dizayn edilməlidir

Əgər əvvəldən bilirsinizsə ki, obyektləriniz dövrə yaradır və ya bərpa zamanı bütün bağlantıların qorunması vacibdirsə — obyektləri deyil, onların identifikatorlarını saxlamaq daha yaxşıdır.

Nümunə: referanslar əvəzinə identifikator saxlamaq

Book sinifini müəllifə istinad üçün identifikator vasitəsilə dəyişdirək:

public class Book
{
    public string Title { get; set; }
    public int Year { get; set; }

    public int AuthorId { get; set; }
}

Müəllifin kitablar siyahısı yerinə — kitabların identifikatorları. Deserializasiya sonrası bağlantıları id-lərə görə "matching" etmək lazım gələcək, amma təhlükəli dövrlər olmayacaq.

Niyə bu real layihələr üçün önəmlidir?

  • Bazalar demək olar həmişə identifikatorlardan istifadə edir, çünki onlar export/import zamanı işləri asanlaşdırır.
  • REST API də adətən id-lərlə mübadilə edir, tam nested mürəkkəb strukturlar deyil.

9. Daxil edilmiş kolleksiyalar: ağacların serialization-u

Tez-tez rastlanan hal — istənilən dərinlikdə ierarxiya: qovluq ağacı, menyu strukturu, kateqoriyalı məhsul kataloqu.

"Ağac" üçün sinif nümunəsi:

public class Folder
{
    public string Name { get; set; }
    public List<Folder> Children { get; set; } = new();
}

Ağacı yaradırıq:

var root = new Folder
{
    Name = "Root",
    Children = new List<Folder>
    {
        new Folder { Name = "Sub1", Children = { new Folder { Name = "Sub1-1" } } },
        new Folder { Name = "Sub2" }
    }
};

Serialize edib çap edirik:

string jsonTree = JsonSerializer.Serialize(root, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(jsonTree);

Belə ağac üçün JSON — daxil edilmiş massivlərlə aydın ierarxiyanı göstərəcək.

10. Array və listlərin serialization xüsusiyyətləri

Əgər bir property array (T[]) və ya kolleksiya (List<T>)dırsa, serialization onu adi JSON-array-a çevirəcək.

public class Shop
{
    public string Name { get; set; }
    public string[] Departments { get; set; }
}
var shop = new Shop
{
    Name = "Universam",
    Departments = new[] { "Ovoschi", "Frukty", "Myaso" }
};

string jsonShop = JsonSerializer.Serialize(shop, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(jsonShop);

JSON təxminən belə olacaq:

{
  "Name": "Universam",
  "Departments": [
    "Ovoschi",
    "Frukty",
    "Myaso"
  ]
}

11. Atributların daxil edilmiş obyektlərə təsiri

Əgər daxil edilmiş obyektlərdə [JsonIgnore] istifadə etsəniz, həmin property-lər istənilən səviyyədəki JSON-a daxil edilməyəcək.

public class SecretBook : Book
{
    [JsonIgnore]
    public string SecretCode { get; set; }
}

Bu yanaşma gizli məlumatları qorumaq üçün geniş istifadə olunur: obyektlərin bəzi daxili sahələrini serialize etmək istəmirsinizsə, atribut əlavə edin — və onlar JSON-da olmayacaq.

12. Praktik məsləhətlər

  • Müsahibələrdə tez-tez soruşurlar: "Ağacı necə serialize etmək olar (Tree)?" və "Dövrəvi referanslarla nə etmək lazımdır?". ReferenceHandler.IgnoreCycles və identifikatorların saxlanması nümunələrini hazır saxlayın.
  • Kommerciya layihələrində sifarişlər, fakturalar, istifadəçilər, məhsul kataloqları, mürəkkəb hesabatlar serialize olunur. Daxililik geniş yayılıb.
  • Əgər qraf və ya ağaclarla işləyirsinizsə, dövrlərdən qaçmağa çalışın və ya id-lərdən istifadə edin.
  • Əgər xarici API ilə işləyirsinizsə, daxil edilmiş strukturların formatını qabaqcadan razılaşdırın ki, JSON parse edərkən sürprizlər olmasın.
2
Tapşırıq
C# SELF, səviyyə, dərs
Bağlanıb
Obyektin kolleksiyalar ilə seriyalaşdırılması
Obyektin kolleksiyalar ilə seriyalaşdırılması
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION