Natomiast deserializacja jest procesem odwrotnym do serializacji, to znaczy dzięki niej możemy odczytać ten strumień bajtów i przywrócić obiekt do stanu sprzed serializacji.
Na pierwszy rzut oka ta definicja może wydawać się trochę skomplikowana, ale w tym artykule pokaże Ci, o co w tym wszystkim chodzi, jak serializować i deserializować dane w C#, czym jest format JSON oraz w jakich sytuacjach może to być użyteczne w programowaniu.
Aplikacja
Od samego początku będziemy pracować na kodzie.
Załóżmy, że musimy stworzyć prostą aplikację, która będzie przechowywała informacje o wszystkich naszych wydatkach. Czyli jeżeli zrobimy zakupy, to później zawsze dodajemy informacje o nich do naszej aplikacji. Dzięki temu na koniec miesiąca będziemy mogli sobie później zrobić jakieś podsumowanie i zobaczyć na co ile pieniędzy zostało wydanych.
W poprzednim artykule pokazywałem Ci, jak stworzyć nową aplikację Windows Forms, także w tym odcinku znowu możemy stworzyć taką aplikację. Jeżeli nie wiesz jak to zrobić, wróć proszę do poprzedniego artykułu. Tam opisuje od zera cały proces. Typ aplikacji nie ma tutaj żadnego znaczenia, w każdej aplikacji stworzonej w C# proces serializacji i deserializacji wygląda tak samo. Także możesz stworzyć zarówno jakąś aplikację desktopową na komputer, mobilną czy webową.
Interfejs użytkownia
Na początek na formatkę główną naszej aplikacji dodamy kilka kontrolek.
To znaczy 2 Labelki, czyli etykiety, które będą opisami dla pól.
TextBoxa do wprowadzania wartości tekstowych. W naszej aplikacji tutaj będziemy wpisywać nazwę produktu, czy po prostu ogólną nazwę zakupów.
Następnie NumerUpDown, do wprowadzania wartości liczbowych. W naszej aplikacji tutaj będziemy wprowadzać kwotę dla danego zakupu.
Będziemy potrzebować też Button, czyli przycisk po kliknięciu którego zakup zostanie dodany do aplikacji.
Wszystkie zakupy wyświetlimy w tabeli na kontrolce DataGridView.
Teraz ustawimy odpowiednio właściwości Text oraz Name dla komponentów. Właściwość Text umożliwia nam wyświetlenie tekstu, a z kolei po wartości wpisanej w Name (tbName, nudAmount, btnAdd, dgvShopping) będziemy się odnosić do kontrolek w code behind.
Tak wygląda nasza aplikacja.
Logika aplikacji
Wizualnie jest już ok, natomiast musimy jeszcze zaimplementować logikę aplikacji. Tutaj właśnie za chwilę skorzystamy z serializacji i deserializacji danych.
Przejdźmy zatem do code behind (klawisz F7 na formularzu lub PPM i View Code)
Dodajmy sobie do naszej aplikacji jeszcze folder Models i wewnątrz tego folderu klasę Purchase i to będzie nasz główny model na którym będziemy pracować.
internal class Purchase
{
public string Name { get; set; }
public decimal Amount { get; set; }
public DateTime CreatedDate { get; set; }
}
Mamy tutaj właściwość Name typu string, jest to nazwa produktu, danego zakupu.
Właściwość Amount będzie przechowywała informacje o kwocie zakupu.
A CreatedDate date utworzenia, dodania zakupu do listy.
Wróć do kodu formularza Form1.cs.
W celu poprawnego wyświetlenia danych na tabeli w DataGridView przygotujemy sobie prywatne pole typu BindingList, w której będziemy przechowywać wszystkie zakupy.
private BindingList<Purchase> _purchases = new BindingList<Purchase>();
Teraz możemy powiązać w konstruktorze listę z DataGridView:
public Form1()
{
InitializeComponent();
dgvShopping.DataSource = _purchases;
}
Dzięki temu, jeżeli teraz dodamy nowy zakup do tej listy, to od razu będzie on widoczny na formatce głównej aplikacji (na DataGridView).
Następnie musimy powiązać zdarzenie Click przycisku btnAdd z metodą. W tym celu najprościej na formatce kliknąć dwukrotnie na przycisk i to powiązanie zostanie automatycznie utworzone. Powinna zostać dodana taka metoda:
private void btnAdd_Click(object sender, EventArgs e)
{
}
Chcemy, żeby po kliknięciu w przycisk Dodaj, został dodany do listy nowy zakup z opisem i kwotą wpisaną na formularzu.
var purchase = new Purchase
{
Amount = nudAmount.Value,
Name = tbName.Text,
CreatedDate = DateTime.UtcNow
};
_purchases.Add(purchase);
Oprócz tego, możemy wyczyścić pola, tak żeby użytkownik mógł ponownie wpisać nazwę i wartość nowego zakupu.
tbName.Text = "";
nudAmount.Value = 0;
Super, możemy teraz uruchomić aplikację (Ctrl + F5) i zobaczymy jak to działa.
Po wpisaniu nazwy zakupu, kwoty oraz kliknięciu przycisk Dodaj – nowy zakup zostaje dodany do listy i jest wyświetlany na DataGridView.
Oczywiście można jeszcze poprawić wizualnie naszą aplikację, tak żeby sama aplikacja, jaki i tabela DataGridView ładnie wyświetlała dane, natomiast na tym dzisiaj nie będziemy się skupiać. Tutaj skupimy się tylko na najważniejszych funkcjonalnościach i logice aplikacji.
Wygląda na to, że wszystko działa prawidłowo. Natomiast zobacz co się stanie, jeżeli teraz zamknę aplikację i uruchomię ją ponownie.
Problem
Zauważ, że wszystkie wcześniej wprowadzone dane zostały usunięte. Wynika to z tego, że przechowujemy informacje o zakupach w polu, które jest tworzone od nowa przy uruchomieniu aplikacji. Jeżeli aplikacje zamkniemy, to tracimy wszystkie dane, które były w naszej aplikacji. Czyli wszystkie informacje, które zapisujemy sobie w różnych polach, zmiennych, czy właściwościach działają tylko podczas uruchomienia aplikacji. Każde zamknięcie aplikacji usuwa te dane.
Jak w takim razie poradzić sobie z tym problemem i sprawić, by aplikacja zawsze zapamiętywała te dane? Tak, żeby zamknięcie aplikacji nie powodowało usuwania danych?
Tak naprawdę możemy sobie z tym poradzić na kilka sposobów.
W tym odcinku właśnie pokaże Ci, jak dzięki serializacji możesz najpierw naszą listę z danymi, czyli obiekt zamienić na tekst w formacie JSON i później zapisać do pliku. A następnie przy uruchomieniu aplikacji najpierw pobrać tekst w formacie JSON z pliki i dzięki deserializacji przywrócić listę z danymi do aplikacji.
Jeżeli to zaimplementujemy, to po zamknięciu aplikacji i ponownym jej uruchomieniu dalej będziemy mieć dostęp do wszystkich danych, czyli w naszym przypadku zapisanych zakupów.
Oczywiście możesz serializować dane do różnego formatu, nie tylko do JSONA. Często też korzysta się np. z formatu XML, natomiast tutaj akurat skorzystamy z popularnego formatu JSON.
Serializacja
Zacznijmy od serializacji. Możemy zapisywać nasze zakupy bezpośrednio po dodaniu nowego zakupu, lub przed zamknięciem aplikacji. Prościej będzie to zrobić pierwszym sposobem, także przejdź proszę do metody btnAdd_Click i tutaj dodamy tą logikę.
Zarówno do serializacji do JSONA, jak i deserializacji z JSON skorzystamy z popularnej biblioteki Newtonsoft.Json. Możemy ją najpierw zainstalować przez NuGeta.
I teraz serializacja jest bardzo prosta, wystarczy wywołać statyczną metodę SerializeObject z klasy JsonConvert i przekazać obiekt, który chcemy serializować.
var json = JsonConvert.SerializeObject(_purchases);
Teraz tekst w formacie JSON zapiszemy sobie w pliku. Przygotujmy najpierw ścieżkę do pliku.
private string _filePath = Path.Combine(Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory), "data.json");
I zapiszemy do tego pliku dane.
File.WriteAllText(_filePath, json);
Super. I to tyle jeżeli chodzi o serializację i zapis do pliku. Teraz jeszcze musimy wczytać zapisane dane przy uruchomieniu aplikacji.
Deserializacja
Wczytywanie możemy zrobić np. w konstruktorze lub podpiąć się pod zdarzenie Load. Skorzystamy z pierwszego sposobu. Najpierw w konstruktorze wczytamy dane z pliku i zapiszemy w zmiennej o nazwie json.
var json = File.ReadAllText(_filePath);
Warto wcześniej jeszcze sprawdzić, czy ten plik istnieje na dysku, bo jeżeli teraz go nie będzie, to zostanie rzucony wyjątek i nasza aplikacja przestanie działać. Dodajmy instrukcje warunkową, która sprawdzi czy plik istnieje. Jeżeli plik nie istnieje, to zostanie on utworzony.
if (!File.Exists(_filePath))
{
File.Create(_filePath).Dispose();
}
var json = File.ReadAllText(_filePath);
Super, jesteśmy już zabezpieczenie w tym miejscu przed taka ewentualnością.
Wracając do deserializacji.
Zmienna, którą utworzyliśmy - json przechowuje tekst w formacie JSON. Teraz wystarczy skorzystać z deserializacji i zamienić JSONa na obiekt w C#, w tym przypadku naszym obiektem będzie BindingList<Purchase> i taki typ generyczny przekażemy do metody statycznej tym razem DeserializeObject tej samej klasy JsonConvert. Jako argument przekażemy tekst w formacie JSON.
_purchases = JsonConvert
.DeserializeObject<BindingList<Purchase>>(json);
Musimy jeszcze w tym miejscu dodać jedno zabezpieczenie, przed sytuacją w której ten plik będzie pusty, ponieważ wtedy lista będzie nullem i zostanie rzucony wyjątek przy próbie dodania nowego zakupu. W tym celu wystarczy po deserializacji dodać instrukcję warunkową, gdzie sprawdzimy wartość listy _purchases i jeżeli jest ona nullem lub pustą listą, to chcemy ją zainicjalizować:
if (_purchases == null || !_purchases.Any())
_purchases = new BindingList<Purchase>();
I tak naprawdę to wszystko.
Refaktoryzacja
Możemy jeszcze poprawić czytelność kodu i wprowadzić małą refaktoryzację. Dodamy kilka bardziej opisowych prywatnych metod i całość będzie wyglądać w ten sposób:
public partial class Form1 : Form
{
private BindingList<Purchase> _purchases = new BindingList<Purchase>();
private string _filePath = Path.Combine(Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory), "data.json");
public Form1()
{
InitializeComponent();
CreateDataFile();
GetData();
dgvShopping.DataSource = _purchases;
}
private void btnAdd_Click(object sender, EventArgs e)
{
AddPurchase();
SaveData();
ClearFields();
}
private void ClearFields()
{
tbName.Text = "";
nudAmount.Value = 0;
}
private void SaveData()
{
var json = JsonConvert.SerializeObject(_purchases);
File.WriteAllText(_filePath, json);
}
private void AddPurchase()
{
var purchase = new Purchase
{
Amount = nudAmount.Value,
Name = tbName.Text,
CreatedDate = DateTime.UtcNow,
};
_purchases.Add(purchase);
}
private void GetData()
{
var json = File.ReadAllText(_filePath);
_purchases = JsonConvert.DeserializeObject<BindingList<Purchase>>(json);
if (_purchases == null || !_purchases.Any())
_purchases = new BindingList<Purchase>();
}
private void CreateDataFile()
{
if (!File.Exists(_filePath))
{
File.Create(_filePath).Dispose();
}
}
}
Możemy teraz uruchomić aplikację i zweryfikować czy wszystko działa zgodnie z naszymi założeniami.
Dodam kilka zakupów.
Zamykam aplikację i uruchamiam ponownie.
Jak widzisz, teraz wszystko działa już zgodnie z naszymi początkowymi założeniami. Zakupy wcześniej dodane nie są usuwane po restarcie aplikacji. Jeżeli dodamy jakiś zakup, zamkniemy aplikację i uruchomimy ją ponownie, to zawsze ten zakup już będzie tam widoczny. I właśnie o coś takiego nam chodziło. Dzięki serializacji i deserializacji możemy w łatwy sposób zapisywać tak dane np. do pliku (ale nie tylko).
JSON
Możemy jeszcze sprawdzić, jak wygląda plik, który przechowuje dane aplikacji (data.json). Możesz go otworzyć w notatniku lub podobnej aplikacji. I ten plik będzie wyglądał w ten sposób:
[{"Name":"Komputer","Amount":12.0,"CreatedDate":"2023-06-08T13:42:05.4222769Z"},{"Name":"Monitor","Amount":2.0,"CreatedDate":"2023-06-08T13:42:09.5546737Z"},{"Name":"Mysz","Amount":1.0,"CreatedDate":"2023-06-08T13:42:15.1898511Z"},{"Name":"Klawiatura","Amount":21.2,"CreatedDate":"2023-06-08T13:42:24.718797Z"}]
Po sformatowaniu będzie on bardziej czytelny:
[{
"Name": "Komputer",
"Amount": 12.0,
"CreatedDate": "2023-06-08T13:42:05.4222769Z"
}, {
"Name": "Monitor",
"Amount": 2.0,
"CreatedDate": "2023-06-08T13:42:09.5546737Z"
}, {
"Name": "Mysz",
"Amount": 1.0,
"CreatedDate": "2023-06-08T13:42:15.1898511Z"
}, {
"Name": "Klawiatura",
"Amount": 21.2,
"CreatedDate": "2023-06-08T13:42:24.718797Z"
}
]
I jak widzisz mamy tutaj zapisane wszystkie elementy w liście, czyli obiekcie, który serializowaliśmy. Każdy obiekt jest oddzielony nawiasami klamrowymi i przecinkiem. Wewnątrz nawiasów mamy przechowywane dane na zasadzie słownikowej, czyli klucz i wartość. To znaczy np. klucz "Name" i wartość "Komputer", klucz "Amount" i wartość "12", klucz "CreatedDate" i wartość "2023-06-08". Dzięki temu możemy później przywrócić te dane i stworzyć na ich podstawie obiekt w C#.
To wszystkie na dzisiaj. Jeżeli taki artykuł Ci się spodobał, to koniecznie dołącz do mojej społeczności – darmowe zapisy, gdzie będziesz również miał dostęp do dodatkowych materiałów i przede wszystkim bonusów. Do zobaczenia w kolejnym artykule.
Poprzedni artykuł - Działania Na Plikach w C# - Praktyka