{<Z Kordian Zadrożny

AI, Strony WWW, Programowanie, Bazy danych

Programowanie od zera: Lekcja 5: Program zyskuje pamięć trwałą (Pliki, JSON i Typy Danych)

utworzone przez | sty 3, 2026 | kurs programowania | 0 komentarzy

Wstęp

Mamy stworzony w poprzednich lekcjach świetny program.

Mamy obiekty, funkcje, menu.

Ale ma on jedną dyskwalifikującą wadę. Działa jak rybka w akwarium, czyli ma krótką pamięć. Wyłączasz komputer, idziesz spać, rano włączasz… i Twoja lista wydatków jest pusta. Wszystkie dane zniknęły.

Dlaczego? Bo trzymaliśmy je w RAM (pamięci operacyjnej). RAM jest szybka, ale ulotna (znika po odcięciu prądu). Aby dane przetrwały, musimy je zapisać na Dysku Twardym.

Dziś nauczymy nasz program pakować walizki (dane) i wysyłać je do magazynu (pliku), a potem je rozpakowywać.

Część 1: Mała Encyklopedia Typów (Teoria)

Zanim zaczniemy rzucać danymi po dysku, musimy uporządkować wiedzę. Komputer inaczej traktuje liczbę 5, a inaczej Listę Transakcji.

Typy Proste (Wartościowe)

To najprostsze klocki. Siedzą bezpośrednio w „pudełku” ze zmienną. Są lekkie.

  • int (Integer) – Liczby całkowite (np. 1, -50, 2025).
  • float / double / decimal– Liczby z przecinkiem. W C# do pieniędzy używamy decimal (bo jest precyzyjny), w Pythonie float – który jest tożsamy z decimalem w C#..
  • bool (Boolean) – Prawda lub Fałsz (True/False). Włącznik światła.
  • char (Character) – Pojedynczy znak (np. 'A', '@').

Typy Złożone (Referencyjne)

To kontenery. Zmienna nie trzyma „mebli”, tylko „adres do magazynu”, gdzie te meble stoją.

  • string (Tekst) – Tak, tekst to typ złożony! To łańcuch znaków (char).
  • List / Array (Lista/Tablica) – Kolekcja elementów.
  • Class / Object (Klasa/Obiekt) – Nasza Transakcja. Złożona struktura.

Dlaczego to ważne przy zapisie?

Liczbę łatwo zapisać w pliku. Ale jak zapisać Obiekt, który ma w środku Opis, Kwotę i Datę? Nie możemy po prostu wrzucić „obiektu” do pliku tekstowego. Musimy go zserializować.

Część 2: JSON – Język, który łączy światy oraz dane na dysku

Serializacja to mądre słowo na „spakowanie”. Zamieniamy nasz skomplikowany Obiekt (meblościankę) na płaski tekst (instrukcję montażu IKEA), który można wysłać przez internet lub zapisać w pliku.

Najpopularniejszym formatem zapisu jest JSON (czyt. dżejson). Wygląda tak:

[
  {
    "opis": "Biedronka",
    "kwota": -50.0,
    "typ": "Wydatek"
  },
  {
    "opis": "Wypłata",
    "kwota": 5000.0,
    "typ": "Przychód"
  }
]

Widzisz? To zwykły tekst, ale sformatowany tak, że i człowiek, i komputer go zrozumieją. Używa go Python, C#, JavaScript, a nawet Twoja smart lodówka.

Zapisywanie na dysku pokażę na przykładzie słowników.

To najprostszy scenariusz. Masz listę słowników (np. proste wydatki bez użycia klas). Pythonowa biblioteka json łyka to bez popijania.

Używamy konstrukcji with open(...). Działa ona jak „samozamykacz” w drzwiach – gwarantuje, że plik zostanie zamknięty po zapisaniu, nawet jak program wyrzuci błąd.

import json

# Nasze dane w pamięci (RAM)
dane = [
    {"towar": "Chleb", "cena": 5.50},
    {"towar": "Masło", "cena": 8.00}
]

# ZAPIS (Serializacja)
# 'w' = write (zapisz/nadpisz)
# encoding='utf-8' = obsługa polskich znaków
with open("zakupy.json", "w", encoding='utf-8') as plik:
    json.dump(dane, plik, indent=4) 
    # indent=4 sprawia, że plik ładnie wygląda (ma wcięcia)

print("Zapisano!")

Pliki, o ile nie podamy ścieżki zapisują się w tym samym katalogu, w którym mamy program główny.

Na zrzucie widzisz kod w VSCode i obok otwartą zawartość pliku json z danymi.

Odczytywanie danych

Teraz w drugą stronę. Program startuje i chce wiedzieć, co było w pliku.

import json

try:
    # ODCZYT (Deserializacja)
    # 'r' = read (czytaj)
    with open("zakupy.json", "r", encoding='utf-8') as plik:
        wczytane_dane = json.load(plik)
        
    print("Co my tu mamy:", wczytane_dane)
    # Wynik: [{'towar': 'Chleb', 'cena': 5.5}, {'towar': 'Masło', 'cena': 8.0}]

except FileNotFoundError:
    print("Plik jeszcze nie istnieje!")

tym razem jak widzisz używamy wczytane_dane = json.load(plik) czyli metoda dump tworzy jsona, a load pobiera.

Część 3: Python kompletny program

W Pythonie obsługa plików jest bardzo elegancja dzięki instrukcji with.

Wyzwanie:

Python nie umie automatycznie zamienić Twojej własnej klasy Transakcja na JSON. Musimy mu pomóc. Każdy obiekt w Pythonie ma ukrytą kieszonkę __dict__, która przechowuje jego dane w formie słownika. Wykorzystamy to!

Kod w Pythonie

Najpierw na samej górze dodaj: import json

import json
import os

# --- 1. FOREMKA (KLASA) ---
# To jest ten element obiektowy.
# Dzięki temu mamy pewność, że każda transakcja ma opis, kwotę i typ.
class Transakcja:
    def __init__(self, opis, kwota, typ):
        self.opis = opis
        self.kwota = kwota
        self.typ = typ

# --- 2. ZMIENNE GLOBALNE ---
historia = []  # Tu będziemy trzymać OBIEKTY (instancje klasy Transakcja)
PLIK_DANYCH = "budzet.json"

# --- 3. FUNKCJE (LOGIKA) ---

def zapisz_dane():
    # To był Twój błąd wcześniej. JSON nie umie zapisać Klasy.
    # Musimy zamienić listę Obiektów na listę Słowników.
    # Używamy t.__dict__, co jest możliwe TYLKO, gdy t jest Obiektem.
    dane_do_zapisu = [t.__dict__ for t in historia]
    
    with open(PLIK_DANYCH, 'w', encoding='utf-8') as plik:
        json.dump(dane_do_zapisu, plik, indent=4)
    print("💾 Dane zostały zapisane na dysku!")

def wczytaj_dane():
    if not os.path.exists(PLIK_DANYCH):
        return
    
    with open(PLIK_DANYCH, 'r', encoding='utf-8') as plik:
        dane_z_pliku = json.load(plik) # To jest lista słowników!
        
        # Konwersja powrotna: Słowniki -> Obiekty
        # Musimy "przelać" dane ze słownika do naszej foremki (Klasy)
        for rekord in dane_z_pliku:
            # Tworzymy NOWY obiekt Transakcja
            t = Transakcja(rekord['opis'], rekord['kwota'], rekord['typ'])
            historia.append(t)
            
    print(f"📂 Wczytano {len(historia)} transakcji.")

def pobierz_kwote(typ_operacji):
    while True:
        try:
            tekst = input(f"Podaj kwotę {typ_operacji}: ").replace(",", ".")
            kwota = float(tekst)
            if kwota <= 0:
                print("Kwota musi być dodatnia!")
                continue
            return kwota
        except ValueError:
            print("Błąd! To nie jest liczba.")

def dodaj_transakcje(typ):
    kwota = pobierz_kwote(typ)
    opis = input(f"Podaj opis ({typ}): ")
    
    # POPRAWKA: Tutaj tworzymy OBIEKT, a nie słownik!
    finalna_kwota = kwota * (-1 if typ == "Wydatek" else 1)
    
    # Wywołujemy konstruktor klasy (__init__)
    nowa_transakcja = Transakcja(opis, finalna_kwota, typ)
    
    historia.append(nowa_transakcja)
    print(f"Zaksięgowano {typ}: {kwota:.2f} zł.")

def pokaz_historie():
    if not historia:
        print("Historia jest pusta.")
        return

    print("\n--- HISTORIA ---")
    suma = 0
    licznik = 1
    
    for rekord in historia:
        # Skoro rekord jest OBIEKTEM, używamy kropki, a nie ['...']
        print(f"{licznik}. [{rekord.typ}] {rekord.opis}: {rekord.kwota:.2f} zł")
        suma += rekord.kwota
        licznik += 1
        
    print(f"SALDO: {suma:.2f} zł")

# --- 4. GŁÓWNA PĘTLA ---
print("--- MONITOR BUDŻETU v5.0 (Python OOP + JSON) ---")

# Na starcie wczytujemy dane (deserializacja)
wczytaj_dane()

while True:
    print("\n1. Przychód | 2. Wydatek | 3. Historia | 4. Koniec")
    wybor = input("Wybierz: ")

    match wybor:
        case '1':
            dodaj_transakcje("Przychód")
        case '2':
            dodaj_transakcje("Wydatek")
        case '3':
            pokaz_historie()
        case '4':
            zapisz_dane() # Zapis (serializacja)
            print("Koniec programu.")
            break
        case _:
            print("Nieznana opcja.")

Część 4: C# – System.Text.Json

W C# (nowoczesnym) mamy wbudowaną, potężną bibliotekę do JSON. Co ciekawe C# jest mądrzejszy niż Python w kwestii obiektów. Potrafi sam „zajrzeć” do Twojej klasy i spakować właściwości publiczne (public) bez żadnych sztuczek z __dict__.

W C# obsługa plików i JSON-a nie jest włączona domyślnie „prosto z pudełka” w głównym kodzie. Musimy na górze pliku „zamówić” odpowiednie narzędzia (przestrzenie nazw):

using System.IO;          // "Input/Output" - Klucze do magazynu (Dysku)
using System.Text.Json;   // Tłumacz, który zamienia Obiekty na Tekst

Serializacja (Pakowanie walizki)

Serializacja to zamiana żywego Obiektu (który ma swoje miejsce w pamięci RAM) na martwy ciąg znaków (napis), który można wysłać mailem albo zapisać w pliku.

W C# robi to metoda JsonSerializer.Serialize.

Ważne: C# zapisze tylko te pola, które są Publiczne (public). Jeśli masz jakieś prywatne sekrety w klasie, zostaną one pominięte.

// 1. Mamy listę obiektów w pamięci
List<Transakcja> lista = new List<Transakcja>();
lista.Add(new Transakcja("Chleb", 5.50m, "Wydatek"));

// 2. Konfiguracja (opcjonalne, ale warto)
// WriteIndented = true sprawia, że JSON nie jest zbitą linijką tekstu,
// tylko ładnie sformatowanym drzewkiem (z wcięciami).
var opcje = new JsonSerializerOptions { WriteIndented = true };

// 3. SERIALIZACJA (Obiekt -> Napis)
string gotowyTekstJson = JsonSerializer.Serialize(lista, opcje);

// Teraz 'gotowyTekstJson' to po prostu zmienna string!

Zapis na dysk (Klasa File)

Skoro mamy już tekst (string), musimy go zrzucić na dysk. W C# najprostszą metodą jest File.WriteAllText. Działa ona brutalnie, ale skutecznie: tworzy plik (lub kasuje stary, jeśli istniał) i wrzuca tam cały tekst.

string nazwaPliku = "budzet.json";

// To jedna linijka, która robi całą magię zapisu
File.WriteAllText(nazwaPliku, gotowyTekstJson);

Deserializacja (Rozpakowywanie)

To jest moment, w którym C# różni się od Pythona. W Pythonie json.load zwracał nam „jakiś słownik”. W C# musimy być precyzyjni. Musimy powiedzieć: „Hej, masz tu tekst, zamień go proszę w Listę Transakcji.

Używamy do tego nawiasów ostrych <TypDanych>.

// 1. Najpierw czytamy tekst z dysku
string wczytanyTekst = File.ReadAllText("budzet.json");

// 2. DESERIALIZACJA (Napis -> Obiekt)
// <List<Transakcja>> - to instrukcja dla kompilatora:
// "Użyj foremki Transakcja i ułóż te obiekty w Liście".
List<Transakcja> mojaLista = JsonSerializer.Deserialize<List<Transakcja>>(wczytanyTekst);

// Teraz 'mojaLista' to znów żywe obiekty!
Console.WriteLine(mojaLista[0].Opis); // Działa!

Co jeśli plik nie istnieje? (File.Exists)

Gdybyśmy spróbowali odczytać plik, którego nie ma (np. przy pierwszym uruchomieniu programu), C# wyrzuciłby błąd (Exception) i zamknął program. Dlatego zawsze, zanim odczytamy, pukamy do drzwi:

if (File.Exists("budzet.json")) 
{
    // Czytaj śmiało
}
else
{
    // Stwórz nową, pustą listę, żeby program miał na czym pracować
    return new List<Transakcja>();
}

Kod w C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO; // DO PLIKÓW
using System.Text.Json; // DO JSON

// --- 1. FOREMKA (KLASA) ---
public class Transakcja
{
    public string Opis { get; set; }
    public decimal Kwota { get; set; }
    public string Typ { get; set; }

    // Konstruktor (żeby łatwo tworzyć nowe obiekty)
    public Transakcja(string opis, decimal kwota, string typ)
    {
        Opis = opis;
        Kwota = kwota;
        Typ = typ;
    }

    // Pusty konstruktor jest potrzebny dla Deserializatora (czasami C# go wymaga)
    public Transakcja() { }
}

class Program
{
    // Stała nazwa pliku
    const string PLIK_DANYCH = "budzet.json";

    // --- 2. FUNKCJE ZAPISU I ODCZYTU ---

    static void ZapiszDane(List<Transakcja> baza)
    {
        // Opcje, żeby JSON był ładny i czytelny (z wcięciami)
        var opcje = new JsonSerializerOptions { WriteIndented = true };

        // Magia: Zamiana Listy Obiektów na Tekst
        string jsonString = JsonSerializer.Serialize(baza, opcje);

        // Zapis do pliku
        File.WriteAllText(PLIK_DANYCH, jsonString);
        Console.WriteLine("💾 Dane zostały zapisane!");
    }

    static List<Transakcja> WczytajDane()
    {
        // Sprawdzamy, czy plik istnieje. Jak nie - zwracamy pustą listę.
        if (!File.Exists(PLIK_DANYCH))
        {
            return new List<Transakcja>();
        }

        // Odczytujemy tekst z pliku
        string jsonString = File.ReadAllText(PLIK_DANYCH);

        // Magia powrotna: Tekst -> Lista Obiektów
        // Musimy podać w <...> na co chcemy zamienić ten tekst
        List<Transakcja> wczytaneDane = JsonSerializer.Deserialize<List<Transakcja>>(jsonString);

        Console.WriteLine($"📂 Wczytano {wczytaneDane.Count} transakcji.");
        return wczytaneDane;
    }

    // --- 3. LOGIKA PROGRAMU ---

    static void DodajTransakcje(List<Transakcja> baza, string typ)
    {
        Console.Write($"Podaj opis ({typ}): ");
        string opis = Console.ReadLine();

        Console.Write("Podaj kwotę: ");
        string tekst = Console.ReadLine();

        if (decimal.TryParse(tekst.Replace(",", "."), NumberStyles.Any, CultureInfo.InvariantCulture, out decimal kwota))
        {
            if (kwota <= 0)
            {
                Console.WriteLine("Kwota musi być dodatnia!");
                return;
            }

            decimal finalnaKwota = (typ == "Wydatek") ? kwota * -1 : kwota;

            // Tworzymy obiekt używając konstruktora
            Transakcja t = new Transakcja(opis, finalnaKwota, typ);

            baza.Add(t);
            Console.WriteLine("Zaksięgowano!");
        }
        else
        {
            Console.WriteLine("To nie jest liczba.");
        }
    }

    static void PokazHistorie(List<Transakcja> baza)
    {
        Console.WriteLine("\n--- HISTORIA ---");
        decimal suma = 0;
        int licznik = 1;

        foreach (Transakcja t in baza)
        {
            Console.WriteLine($"{licznik}. [{t.Typ}] {t.Opis}: {t.Kwota}");
            suma += t.Kwota;
            licznik++;
        }
        Console.WriteLine($"SALDO: {suma}");
    }

    // --- 4. MAIN (GŁÓWNA PĘTLA) ---

    static void Main()
    {
        Console.WriteLine("--- MONITOR BUDŻETU v5.0 (C# + JSON) ---");

        // Na starcie wczytujemy dane z pliku!
        List<Transakcja> historia = WczytajDane();

        while (true)
        {
            Console.WriteLine("\n1. Przychód | 2. Wydatek | 3. Historia | 4. Koniec");
            Console.Write("Wybierz: ");
            string wybor = Console.ReadLine();

            switch (wybor)
            {
                case "1":
                    DodajTransakcje(historia, "Przychód");
                    break;
                case "2":
                    DodajTransakcje(historia, "Wydatek");
                    break;
                case "3":
                    PokazHistorie(historia);
                    break;
                case "4":
                    ZapiszDane(historia); // Zapisujemy przed wyjściem
                    return;
                default:
                    Console.WriteLine("Nieznana opcja.");
                    break;
            }
        }
    }
}

skompiluj program i uruchom z exe, który znajdziesz w folderze bin projektu, w moim przypadku:

podaj dane, kliknij koniec, i ponownie uruchom program

Co się właśnie stało?

Stworzyliśmy system, który jest trwały.

  1. Uruchom program. Dodaj „Pensja: 5000”. Zamknij program.
  2. Zajrzyj do folderu z projektem. Powinien tam być plik budzet.json. Otwórz go w notatniku. Zobaczysz swoje dane!
  3. Uruchom program ponownie. Twoja pensja tam jest!

Przybornik Lekcji 5

KomendaJęzykCo to robi?
json.dump(dane, plik)PythonPakuje dane do formatu JSON i wrzuca do otwartego pliku.
json.load(plik)PythonCzyta plik JSON i zamienia go na listy/słowniki Pythona.
with open(...)Python„Inteligentne” otwarcie pliku. Gwarantuje, że plik zostanie zamknięty po użyciu (nawet jak program wybuchnie).
t.__dict__PythonMagiczna właściwość obiektu. Zwraca jego wnętrze w formie słownika.
File.WriteAllText(...)C#Tworzy plik i wpisuje do niego podany tekst. Szybkie i proste.
JsonSerializer.Serialize()C#Zamienia obiekt (lub listę obiektów) na napis w formacie JSON.
JsonSerializer.Deserialize<Typ>()C#Zamienia napis JSON z powrotem na żywy obiekt danego typu.

Podsumowanie różnic (Python vs C#)

CechaPython (json)C# (System.Text.Json)
Podejście„Luźne”. Zamienia obiekt na słownik, a potem słownik na tekst.„Sztywne”. Analizuje typ obiektu i mapuje go bezpośrednio na JSON.
WymaganiaWymaga triku z __dict__ lub pisania własnego enkodera.Działa automatycznie dla właściwości publicznych (public).
OdczytZwraca słowniki (trzeba ręcznie zamienić je z powrotem na obiekty).Zwraca gotowe Obiekty! (Pod warunkiem, że podasz typ w <...>).
BezpieczeństwoJeśli JSON nie pasuje do obiektu, błąd wyskoczy dopiero przy użyciu.Jeśli JSON nie pasuje, błąd wyskoczy już przy wczytywaniu (Deserializacji).

Co dalej? (Lekcja 6)

Teraz zapisujemy wszystko w jednym pliku tekstowym. To działa super w domu, ale w banku by nie przeszło. W następnej, finałowej lekcji kursu podstawowego, zrobimy krok w stronę profesjonalizmu:

Zamienimy plik JSON na Prawdziwą Bazę Danych (SQLite). Uczymy się języka SQL mam nadzieję, z moim drugim kursem: SQL i zintegrujemy go z naszym kodem.

To będzie wielki finał pierwszej, coś sensownego robiącej aplikacji.

Tak, wiem, że strasznie dużo materiału, ale mam nadzieję, że realne wyniki bardziej do Ciebie przemawiają niż czysta teoria.

W lekcji 7 za to, zrobimy dokładnie to samo, ale w trzecim systemie, w TypeScript!

0 komentarzy

Wyślij komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Share This

Share this post with your friends!