File.WriteAllText, File.ReadAllText, File.Exists, JSON, JsonSerializerdotnet new console, vlož kód do Program.cs a spusť
dotnet run. Soubory pak najdeš přímo ve složce projektu — a to je
právě ta nejlepší část, protože si je otevřeš v poznámkovém bloku a uvidíš,
co tvůj program napsal.
Tenhle blok je jen pro vás — dětem ho nepromítejte.
| Fáze | Co se děje | Čas |
|---|---|---|
| Rozjezd | „Co se stane s proměnnou, když program skončí?" | 5 min |
| Text do souboru | Bloky 2–4 + ukázky 1–2 | 20 min |
| Kde ten soubor je | Blok 5, společné hledání na disku | 10 min |
| JSON | Bloky 6–7 + ukázky 3–4 | 25 min |
| Uložená hra | Ukázka 5 | 10 min |
| Cvičení | Děti pracují samy, vy obcházíte | 55 min |
| Závěr | Kdo si otevřel svůj JSON v poznámkovém bloku? | 10 min |
Dnešní dílny nemají tlačítko ▶ Spustit a je to tak správně. Generátor
stránek pozná, že kód obsahuje File., Directory. nebo StreamWriter,
a živé spouštění pro takový blok sám vypne — v prohlížeči totiž není disk,
na který by se dalo zapsat. Blok se vykreslí jen jako kód s poznámkou
„Tenhle kód potřebuje soubory, síť nebo vlákna…".
Praktický důsledek pro hodinu: děti musí mít funkční VS Code s .NET SDK z úvodní hodiny. Než začnete, nechte je ověřit, že v terminálu funguje:
dotnet --version
dotnet new console -o csquest-soubory
cd csquest-soubory
dotnet run
Doporučuju založit jeden projekt na celou hodinu a jen přepisovat
Program.cs. Ušetříte tím deset minut a spoustu zmatků se složkami.
Pozor na to, kde soubory vznikají. Relativní cesta "denik.txt" se počítá
od pracovní složky, což je u dotnet run složka projektu. Děti tam soubor
najdou hned vedle Program.cs — nechte je ho otevřít, ten okamžik stojí za to.
Celá hodina je o jedné větě: paměť je dočasná, disk je trvalý.
Začněte otázkou „Kam se poděla proměnná, když program skončil?" a nechte
je hádat. Teprve pak ukažte File.WriteAllText.
Druhý cíl je pochopit, proč nestačí ukládat holý text: jakmile má postava pět vlastností a seznam předmětů, potřebuješ formát. A ten už někdo vymyslel — jmenuje se JSON a používá ho skoro každá aplikace na světě.
StreamWriter, FileStream). File.WriteAllText stačí na
všechno, co dnes potřebujeme. Streamy patří k velkým souborům, a ty neřešíme.try/catch. Místo nich používáme File.Exists.
Je to jednodušší a děti to samy navrhnou.[JsonPropertyName]. Třídy přijdou
v 16. hodině; dnes stačí Dictionary<string, object>.Jak pracovat se smíšenou úrovní. Cvičení 1 a 2 zvládnou všichni — jsou to jen texty a řádky. Cvičení 3 a 4 jsou JSON a tam se tempo rozejde; slabším nechte předepsaný slovník a ať doplní jen zápis a čtení. Cvičení 5 je projektové a dá se odnést domů.
int zlato = 250;
Console.WriteLine($"Mám {zlato} zlaťáků.");
// Program skončí…
// …a zlato je pryč. Při dalším spuštění začínáme od nuly.
Proměnné žijí v operační paměti. Ta je rychlá, ale úplně zapomnětlivá: jakmile program skončí, systém jí paměť sebere a je po všem.
Když chceš, aby si hra pamatovala postavu i zítra, musíš data dostat na disk — do souboru. A soubor je jenom pojmenovaný kus textu (nebo dat), který na disku zůstane, dokud ho někdo nesmaže.
V C# na to slouží třída File ze jmenného prostoru System.IO.
Nemusíš nic doplňovat, v konzolové aplikaci je k dispozici rovnou.
Dvě metody ti pokryjí devadesát procent všeho:
File.WriteAllText(cesta, text) — zapíše text do souboru,File.ReadAllText(cesta) — přečte celý soubor jako jeden string.File.WriteAllText("denik.txt", "Den 1: Vstoupil jsem do dungeonu.\n");
File.AppendAllText("denik.txt", "Den 2: Potkal jsem skřeta.\n");
File.AppendAllText("denik.txt", "Den 3: Skřet potkal můj meč.\n");
string obsah = File.ReadAllText("denik.txt");
Console.WriteLine(obsah);
string[] radky = File.ReadAllLines("denik.txt");
Console.WriteLine($"Deník má {radky.Length} zápisů.");
Pozor na jeden zásadní rozdíl:
| Metoda | Co udělá |
|---|---|
File.WriteAllText |
soubor přepíše — co tam bylo, je pryč |
File.AppendAllText |
text přidá na konec |
File.ReadAllText |
vrátí celý soubor jako jeden string |
File.ReadAllLines |
vrátí string[] — jeden řádek = jeden prvek |
File.WriteAllLines |
zapíše pole řetězců, každý na vlastní řádek |
WriteAllText je nejčastější zdroj slz: člověk chce přidat zápis do deníku,
omylem použije WriteAllText a přijde o všechno předchozí.
Pokud soubor neexistuje, WriteAllText i AppendAllText ho vyrobí.
Zápis tedy nikdy nespadne na tom, že soubor chybí — na rozdíl od čtení.
Znak \n na konci znamená odřádkování. Bez něj by se všechny zápisy slily
do jednoho dlouhého řádku.
string cesta = "postava.txt";
if (File.Exists(cesta))
{
Console.WriteLine("Načítám uloženou postavu…");
Console.WriteLine(File.ReadAllText(cesta));
}
else
{
Console.WriteLine("Žádná uložená postava. Zakládám novou.");
File.WriteAllText(cesta, "Arkon;1;100\n");
}
Čtení neexistujícího souboru program shodí. Hláška vypadá takhle:
Unhandled exception. System.IO.FileNotFoundException:
Could not find file '/…/postava.txt'.
Řešení je jednoduché a přečteš ho nahlas: „Jestli ten soubor existuje, načti ho; jinak založ nový."
string cesta = "postava.txt";
if (File.Exists(cesta)) { /* načti */ }
else { /* založ */ }
Přesně takhle se chová každá hra, kterou jsi kdy hrál: při startu se podívá, jestli má uloženou pozici. Když má, nabídne „Pokračovat". Když nemá, nabídne jen „Nová hra".
Existenci složky se ptáš metodou Directory.Exists, vyrobíš ji pomocí
Directory.CreateDirectory — ta se nezlobí, ani když už složka existuje.
Console.WriteLine("Pracovní složka:");
Console.WriteLine(Directory.GetCurrentDirectory());
string cesta = Path.Combine("data", "postava.json");
Console.WriteLine($"Relativní cesta: {cesta}");
Directory.CreateDirectory("data");
File.WriteAllText(cesta, "zatím prázdno");
Console.WriteLine("Soubory ve složce data:");
foreach (string soubor in Directory.GetFiles("data"))
{
Console.WriteLine(" " + soubor);
}
Když napíšeš jen "denik.txt", je to relativní cesta. Počítá se
od pracovní složky — a to je při dotnet run složka tvého projektu.
Soubor tedy najdeš hned vedle Program.cs.
Jistotu máš vždycky: Directory.GetCurrentDirectory() ti řekne,
kde se program právě „nachází".
Cesty nikdy neslepuj ručně pomocí + "/" +. Windows používá \,
macOS a Linux /. Od toho je Path.Combine, který dosadí správný oddělovač:
string cesta = Path.Combine("data", "ulozene", "hra.json");
Console.WriteLine(cesta);
A Directory.CreateDirectory("data") je bezpečná i při opakovaném volání —
pokud složka existuje, prostě neudělá nic.
{
"jmeno": "Arkon",
"uroven": 3,
"zivoty": 80,
"inventar": [
"Meč",
"Lektvar",
"Zlatý klíč"
]
}
Holý text stačí na deník, ale ne na postavu. Jakmile máš jméno, úroveň, životy a seznam předmětů, potřebuješ formát, ve kterém se dá poznat, co je co. Tím formátem je JSON.
Pravidla jsou čtyři a naučíš se je za minutu:
{ } = objekt, tedy dvojice klíč: hodnota,[ ] = seznam,Vypadá to jako C#? Trochu ano — a to je záměr. JSON vymysleli tak, aby ho člověk přečetl a počítač zpracoval. Používá ho tvůj mobil, každá webová aplikace i nastavení Minecraftu.
A hlavně: JSON je pořád jen text. Můžeš ho otevřít v poznámkovém bloku a ručně opravit hrdinovi životy. (Čemuž se říká cheatování.)
using System.Text.Json;
var postava = new Dictionary<string, object>
{
["jmeno"] = "Arkon",
["uroven"] = 3,
["zivoty"] = 80,
["inventar"] = new List<string> { "Meč", "Lektvar" }
};
var nastaveni = new JsonSerializerOptions { WriteIndented = true };
string text = JsonSerializer.Serialize(postava, nastaveni);
File.WriteAllText("postava.json", text);
Console.WriteLine(text);
Serializace znamená „převeď data v paměti na text". V C# to dělá
JsonSerializer.Serialize.
Postavu si připravíme jako slovník Dictionary<string, object>:
klíč je název vlastnosti, hodnota může být číslo, text i celý seznam.
Typ object znamená „cokoliv" — a serializátor si sám pozná, o co jde.
new JsonSerializerOptions { WriteIndented = true } zapne hezké odsazování.
Bez něj se JSON zapíše do jednoho dlouhého řádku — počítači to nevadí,
ale ty si ho pak nepřečteš.
Serializátor ve výchozím nastavení převede háčky a čárky na únikové kódy:
"Me\u010D" místo "Meč". Není to chyba — při čtení zpátky
vyjde správně „Meč". Kdo chce v souboru vidět rovnou češtinu, přidá
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping (a nahoru
using System.Text.Encodings.Web;).
using System.Text.Json;
string text = File.ReadAllText("postava.json");
var data = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(text)
?? new Dictionary<string, JsonElement>();
string jmeno = data["jmeno"].GetString() ?? "bezejmenný";
int uroven = data["uroven"].GetInt32();
Console.WriteLine($"{jmeno}, úroveň {uroven}");
foreach (JsonElement polozka in data["inventar"].EnumerateArray())
{
Console.WriteLine(" • " + polozka.GetString());
}
Deserializace je cesta zpátky: z textu na data. Načteme soubor jako
string a necháme JsonSerializer udělat zbytek.
Výsledek si bereme jako Dictionary<string, JsonElement>.
JsonElement je „ještě nerozbalená hodnota" — a ty jí řekneš,
jak ji chceš přečíst:
| Metoda | Vrátí |
|---|---|
.GetString() |
text (může být null, proto ?? "") |
.GetInt32() |
celé číslo |
.GetDouble() |
desetinné číslo |
.GetBoolean() |
true / false |
.EnumerateArray() |
prvky seznamu, dají se projít foreach |
Dvojité otazníky ?? znamenají „a když by tam bylo nic, vezmi tohle".
Deserialize totiž může vrátit null, když je soubor prázdný.
Klíč, který v souboru není, program shodí (KeyNotFoundException).
Proto se u nepovinných věcí ptej: if (data.ContainsKey("zlato")).
Proč zrovna File.WriteAllText. Je to obal nad StreamWriter, který
otevře, zapíše, zavře a uklidí. Pro soubory do jednotek megabajtů je to
naprosto správná volba. Streamy mají smysl až tam, kde se soubor nevejde
do paměti — což ve výuce nenastane.
Kódování. File.WriteAllText zapisuje UTF-8 bez BOM. Čeština je tedy
v pořádku na všech třech systémech. Kdyby dítě otevřelo soubor ve starém
poznámkovém bloku na Windows a vidělo paskvil, jde o kódování zobrazení,
ne o chybu programu.
Konce řádků. \n funguje všude; Windows si historicky psal \r\n.
File.ReadAllLines si s obojím poradí, takže se tím nemusíme zabývat.
Kdo chce být korektní, použije Environment.NewLine.
Dictionary<string, object> versus třídy. System.Text.Json serializuje
hodnotu typu object podle jejího skutečného typu za běhu, takže
int, string i List<string> se uloží správně. Při čtení už ale
object nestačí — proto čteme do JsonElement. V 16. hodině, až budou
třídy, se z toho stane JsonSerializer.Deserialize<Postava>(text) a atribut
[JsonPropertyName("jmeno")] pro případ, kdy se název vlastnosti v C#
liší od klíče v souboru.
Proč tu není try/catch. Záměrně. File.Exists pokryje 95 % případů
a je čitelnější. Výjimky nechte na pozdější hodinu — dnes by jen zamlžily
pointu. Kdo se zeptá, řekněte pravdu: „File.Exists neochrání proti tomu,
že soubor někdo smaže o milisekundu později; na to jsou výjimky."
Verze uložené hry. Ve cvičení 5 se do JSONu ukládá klíč "verze".
Vypadá to jako zbytečnost, ale je to naprosto reálná praxe: jakmile
změníte strukturu uložené hry, staré soubory se rozbijí. Kontrola verze
je nejlevnější pojistka na světě a děti to většinou znají z modů do her.
Bezpečnostní poznámka do diskuze. Nikdy neukládejte hesla do souboru jako čitelný text. Zeptejte se, proč — dojdou k tomu sami.
Dílny v téhle hodině nemají tlačítko ▶ Spustit — sahají na soubory
a to v prohlížeči nejde. Kód zkopíruj do Program.cs ve svém projektu
a spusť dotnet run. Soubory, které vzniknou, si pak otevři v editoru.
AppendAllText za WriteAllText a podívej se, co se stane.for přes pole řádků).dotnet run.Co si z toho odnést: WriteAllText zakládá, AppendAllText přidává.
Až si soubor denik.txt otevřeš v editoru, uvidíš přesně to, co program vypsal.
█ podle skóre děleného deseti.dotnet run.Co si z toho odnést: textový soubor s oddělovačem ; je nejjednodušší
možná „databáze". Split(';') rozřeže řádek, int.Parse udělá z textu číslo.
"zlato" s nějakým číslem.WriteIndented = false a porovnej, jak soubor vypadá.text.Length).dotnet run.Co si z toho odnést: slovník v paměti a JSON v souboru jsou dvě podoby
té samé věci. Serialize je převede tam, Deserialize zpátky.
█ (jeden znak za deset životů).data.ContainsKey("zlato") a vypiš zlato jen když je.dotnet run.Co si z toho odnést: File.Exists je pojistka, JsonElement je „zabalená
hodnota" a .GetString() / .GetInt32() ji rozbalí na správný typ.
"verze" na 2 a spusť znovu."cas_hrani" s počtem minut.dotnet run.Co si z toho odnést: tohle už je opravdová uložená hra. Spusť ji několikrát a uvidíš, jak hrdinovi roste úroveň i zlato — protože se to pamatuje.
Unhandled exception. System.IO.FileNotFoundException:
Could not find file '/Users/ty/csquest/postava.json'.
Nejčastější chyba hodiny. Program chce číst soubor, který ještě neexistuje. V hlášce je rovnou celá cesta — přečti si ji, řekne ti přesně, kde program soubor hledal. Často zjistíš, že hledal jinde, než sis myslel.
Oprava je vždycky stejná: před čtením se zeptej.
string cesta = "postava.json";
if (File.Exists(cesta)) { /* čti */ }
else { /* založ výchozí */ }
Druhá klasika je KeyNotFoundException při čtení JSONu — v souboru prostě
ten klíč není (třeba proto, že jsi ho přejmenoval). Ověř si ho pomocí
data.ContainsKey("zlato"), nebo si soubor otevři a podívej se.
| Hláška / projev | Co to znamená | Jak to opravit |
|---|---|---|
FileNotFoundException |
soubor neexistuje | if (File.Exists(cesta)) před čtením |
DirectoryNotFoundException |
chybí složka v cestě | Directory.CreateDirectory("data") |
KeyNotFoundException |
klíč v JSONu není | data.ContainsKey("klic") |
JsonException: ... is invalid |
poškozený JSON (čárka navíc) | otevři soubor a oprav ho |
InvalidOperationException u GetInt32 |
hodnota není číslo | čti ji GetString(), nebo oprav soubor |
| soubor je pořád prázdný | zápis se nespustil, nebo se přepsal | zkontroluj WriteAllText vs AppendAllText |
| starý obsah zmizel | WriteAllText soubor přepisuje |
použij AppendAllText |
CS0103: The name 'JsonSerializer' does not exist |
chybí using |
nahoru přidej using System.Text.Json; |
v souboru jsou kódy \u010D |
výchozí escapování češtiny | nevadí, při čtení vyjde správně |
Všechna dnešní cvičení pracují se soubory, takže se nespouštějí
v prohlížeči. Postup je pokaždé stejný: kód si zkopíruj do Program.cs
ve svém projektu, spusť dotnet run a pak se podívej do složky projektu,
jaké soubory tam přibyly. Nápovědy „💡 Poradit" fungují normálně —
jen ti kód nevloží do editoru, ale poradí slovy.
Zadání:
Hrdina si vede deník. Ten musí přežít i vypnutí počítače.
- Založ soubor
denik.txta zapiš do něj první zápis💡 Poradit
Zakládá se jedním řádkem:File.WriteAllText(CESTA, "Den 1: …\n");
Pokud soubor neexistuje, vyrobí se. Pokud existuje, přepíše se.
Znak\nna konci je odřádkování — bez něj se zápisy slijí.- Přidej další tři zápisy, aniž bys ty předchozí smazal
💡 Poradit
Na přidávání jeFile.AppendAllText(CESTA, "Den 2: …\n");
Kdybys znovu použilWriteAllText, zůstal by v souboru jen poslední zápis.
Zkus to schválně a podívej se, co se stane — pochopíš to líp než z vysvětlení.- Přečti celý deník a vypiš ho na obrazovku
💡 Poradit
string obsah = File.ReadAllText(CESTA);vrátí celý soubor jako jeden text.
Pak ho stačí vypsat:Console.WriteLine(obsah);
Pro jistotu se nejdřív zeptej:if (File.Exists(CESTA)).- Vypiš zápisy očíslované a řekni, kolik jich je
💡 Poradit
string[] radky = File.ReadAllLines(CESTA);rozdělí soubor po řádcích.
Počet zápisů jeradky.Length.
Očíslování:for (int i = 0; i < radky.Length; i++) Console.WriteLine($"{i + 1}. {radky[i]}");- Vypiš jen ty zápisy, ve kterých se objeví hledané slovo
💡 Poradit
Hledané slovo si dej do proměnné:string HLEDANE = "skřet";
V cyklu přes řádky se ptej:if (radek.ToLower().Contains(HLEDANE))ToLower()zajistí, že najdeš i „Skřet" s velkým S.
| Úroveň | Co má zvládnout |
|---|---|
| Minimum | Body 1–3, deník se založí, doplní a vypíše |
| Rozšíření | Bod 4, očíslované zápisy a jejich počet |
| Výzva | Bod 5 a navíc počet nalezených zápisů |
dotnet run.Očekávaný výsledek: soubor denik.txt se čtyřmi řádky ve složce projektu,
vypsaný deník, očíslované zápisy a dva nalezené zápisy se slovem „skřet".
Kde se zaseknou:
WriteAllText. V souboru zůstane jen poslední zápis.
Je to nejlepší možná ukázka rozdílu — nechte je to udělat.\n. Celý deník se slije do jednoho řádku a ReadAllLines
pak vrátí jediný prvek. Poznají to podle Zápisů celkem: 1.ToLower() najdou jen jeden ze dvou
zápisů. Dobrá příležitost mluvit o tom, že počítač je doslovný.Directory.GetCurrentDirectory()
a soubor opravdu otevřít. Ten moment je pro spoustu dětí ten nejsilnější
z celé hodiny.Kdo je hotový dřív: ať program upraví tak, aby se při každém spuštění
přidal jeden nový zápis s aktuálním datem (DateTime.Now.ToString("d.M.yyyy"))
a deník tak rostl.
Otázka na závěr cvičení: „Proč je WriteAllText nebezpečnější než AppendAllText?"
Odpověď: protože nenávratně přepíše, co v souboru bylo.
dotnet run.Zadání:
Nejlepší výsledky musí přežít restart. Ulož je do souboru a zase načti.
- Zapiš do
skore.txtčtyři řádky ve tvarujmeno;skore💡 Poradit
Nejdřív si připrav pole textů:string[] zaznamy = { "Arkon;1250", "Elara;980", … };
Zapíšeš je najednou:File.WriteAllLines(CESTA, zaznamy);
Každý prvek pole skončí na vlastním řádku.- Načti řádky, rozřež je a ulož do seznamu n-tic
💡 Poradit
string[] casti = radek.Split(';');rozřeže řádek na dva kusy.casti[0]je jméno,casti[1]je skóre — ale jako text.
Na číslo ho převedeš:int.Parse(casti[1])
A přidáš:tabulka.Add((casti[0], int.Parse(casti[1])));- Seřaď žebříček od nejvyššího skóre a vypiš ho s pořadím
💡 Poradit
var serazeno = tabulka.OrderByDescending(h => h.skore).ToList();
Pořadí vypíšeš klasickýmforcyklem přes index.
Zarovnání:{serazeno[i].jmeno.PadRight(8)}a{serazeno[i].skore,5}.- Přidej nový výsledek a ulož celou tabulku zpátky do souboru
💡 Poradit
Nejdřív do seznamu:tabulka.Add(("Nová hrdinka", 1420));
Pak z n-tic udělej zase řádky:var vystup = new List<string>();foreach (var h in serazeno) vystup.Add($"{h.jmeno};{h.skore}");
A zapiš:File.WriteAllLines(CESTA, vystup);- Vypiš statistiku: nejlepší, nejhorší a průměrné skóre
💡 Poradit
LINQ to umí jedním voláním:tabulka.Max(h => h.skore),tabulka.Min(h => h.skore),tabulka.Average(h => h.skore)
Průměr je desetinné číslo, zaokrouhli ho:$"{prumer:F0}"
Sloupec k výsledku:new string('█', h.skore / 200)
| Úroveň | Co má zvládnout |
|---|---|
| Minimum | Body 1–2, řádky se načtou a rozřežou |
| Rozšíření | Body 3–4, seřazený žebříček uložený zpátky |
| Výzva | Bod 5 se statistikou a sloupcovým grafem |
dotnet run.Očekávaný výsledek: pět hrdinů seřazených od 1640 dolů, soubor skore.txt
přepsaný do nového pořadí a statistika s průměrem 1279 bodů.
Kde se zaseknou:
int.Parse na textu s mezerou. Pokud si dopíšou záznam jako
"Nyra; 1420", Parse spadne na FormatException. Řešení je
int.Parse(casti[1].Trim()) — a je to dobrá lekce o tom, že data z venku
bývají špinavá..ToList(). OrderByDescending vrací IEnumerable,
které nemá Count ani indexer [i].tabulka místo serazeno.
Funguje to, ale soubor pak není seřazený — ať si ho otevřou a uvidí.Kdo je hotový dřív: ať program spustí dvakrát a vysvětlí, proč se tabulka
podruhé neznásobila (protože WriteAllLines na začátku soubor přepíše).
Pak ať to změní tak, aby se skóre opravdu hromadilo.
Otázka na závěr cvičení: „Proč se skóre v souboru musí převádět z textu na číslo?"
Odpověď: v souboru je jen text; "980" > "1640" by se porovnalo podle abecedy.
dotnet run.Zadání:
Textový soubor na postavu nestačí. Ulož ji ve formátu, kterému rozumí celý svět.
- Připrav postavu jako
Dictionary<string, object>💡 Poradit
Nahoru patříusing System.Text.Json;
Slovník se zapisuje takhle:var postava = new Dictionary<string, object> { ["jmeno"] = "Arkon", ["uroven"] = 3 };
Hodnota smí být číslo, text i celýList<string>.- Převeď postavu na JSON s odsazením a vypiš text
💡 Poradit
var nastaveni = new JsonSerializerOptions { WriteIndented = true };string text = JsonSerializer.Serialize(postava, nastaveni);
BezWriteIndentedby byl celý JSON na jediném řádku.- Ulož JSON do souboru a vypiš, kam ses ho uložil
💡 Poradit
File.WriteAllText("postava.json", text);
Kde soubor skončil, zjistíš takhle:Console.WriteLine(Directory.GetCurrentDirectory());
Otevři si ho pak v editoru — je to obyčejný text.- Načti soubor zpátky a vypiš jméno, úroveň a životy
💡 Poradit
var data = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(File.ReadAllText(CESTA)) ?? new Dictionary<string, JsonElement>();
Text vytáhneš.GetString() ?? "", číslo.GetInt32().
Před čtením se zeptejif (File.Exists(CESTA)).- Vypiš inventář a poskládej z toho kartu postavy
💡 Poradit
Seznam se prochází přesEnumerateArray:foreach (JsonElement polozka in data["inventar"].EnumerateArray())
Uvnitř:Console.WriteLine(" • " + polozka.GetString());
Rámeček karty poskládej ze znaků─a║jako v ukázce 4.
| Úroveň | Co má zvládnout |
|---|---|
| Minimum | Body 1–3, JSON se vypíše a uloží |
| Rozšíření | Bod 4, načtení zpátky a výpis vlastností |
| Výzva | Bod 5 — hotová karta postavy i s inventářem |
dotnet run.Očekávaný výsledek: vypsaný JSON, soubor postava.json ve složce projektu
a na konci vykreslená karta postavy s inventářem o třech položkách.
Kde se zaseknou:
using System.Text.Json;. Hláška CS0103: The name
'JsonSerializer' does not exist. Připomeňte, že using patří úplně nahoru.\u010D v souboru. Bude to první, čeho si všimnou. Uklidněte je:
je to normální escapování, po načtení vyjde „Meč" správně.Deserialize vrací null. Bez ?? new Dictionary<…>() se objeví
varování a potenciální NullReferenceException. Vysvětlete ?? jako
„a když nic, tak tohle"..GetInt32() na textové hodnotě. InvalidOperationException.
Ať si soubor otevřou a podívají se, jestli je hodnota v uvozovkách.Kdo je hotový dřív: ať si soubor ručně upraví v editoru (změní životy), program spustí znovu bez části, která postavu ukládá, a uvidí, že karta ukazuje jeho ruční změnu. To je aha moment celé hodiny.
Otázka na závěr cvičení: „Proč je JSON lepší než náš jmeno;skore?"
Odpověď: unese vnořené seznamy, pojmenované klíče a přečte ho i cizí program.
dotnet run.Zadání:
Příšery nepatří do kódu, ale do datového souboru. Pak je můžeš měnit bez překládání hry.
- Vyrob bestiář jako seznam slovníků a ulož ho do
bestiar.json💡 Poradit
Jedna příšera je slovník, bestiář je jejich seznam:var bestiar = new List<Dictionary<string, object>>();bestiar.Add(new Dictionary<string, object> { ["jmeno"] = "Skřet", ["zivoty"] = 30, ["utok"] = 6 });
Uložení je stejné jako u postavy:JsonSerializer.Serialize(bestiar, nastaveni).- Načti soubor zpátky a vypiš jména všech příšer
💡 Poradit
Typ při čtení je seznam slovníků sJsonElement:var nactene = JsonSerializer.Deserialize<List<Dictionary<string, JsonElement>>>(text) ?? new List<Dictionary<string, JsonElement>>();
Pak stačíforeach (var prisera in nactene) Console.WriteLine(prisera["jmeno"].GetString());- Vypiš přehlednou tabulku: jméno, životy, útok
💡 Poradit
Zarovnání drží tabulku pohromadě:$"{jmeno.PadRight(14)} {zivoty,4} HP {utok,3} ATK"
Čísla čti přesprisera["zivoty"].GetInt32().
Nad tabulku si vypiš hlavičku a čáru z─.- Najdi nejsilnější příšeru podle útoku
💡 Poradit
Klasický průchod s pamatováním nejlepšího:string nejJmeno = "";aint nejUtok = -1;
V cyklu:if (utok > nejUtok) { nejUtok = utok; nejJmeno = jmeno; }
Začíná se od-1, aby první příšera vždycky vyhrála.- Seřaď bestiář podle životů a dokresli sloupce
💡 Poradit
Nejdřív si přelij data do n-tic, ať se dobře řadí:var prehled = new List<(string jmeno, int zivoty, int utok)>();
Pak:prehled.OrderByDescending(p => p.zivoty).ToList()
Sloupec:new string('█', p.zivoty / 10)
| Úroveň | Co má zvládnout |
|---|---|
| Minimum | Body 1–2, bestiář se uloží a načte |
| Rozšíření | Body 3–4, tabulka a nejsilnější příšera |
| Výzva | Bod 5 se seřazeným žebříčkem a sloupci |
dotnet run.Očekávaný výsledek: soubor bestiar.json s polem pěti objektů, vypsaná
tabulka, Drak jako nejsilnější (25 ATK) a žebříček podle životů se sloupci.
Kde se zaseknou:
Dictionary<…>, ale v souboru je pole,
takže je potřeba List<Dictionary<string, JsonElement>>. Ať se podívají
do souboru: začíná hranatou závorkou, tedy seznam.nejUtok = 0 místo -1. U příšery s útokem 0 by se nikdy nevybrala.
U našich dat to projde, ale je to dobrá příležitost mluvit o okrajových případech.new Dictionary<string, object> { … }. Působí to hrozivě.
Zdůrazněte, že v 16. hodině to nahradí jedna třída Prisera.?? "neznámá". Bez toho se objeví varování o null.Kdo je hotový dřív: ať do bestiáře přidá klíč "odolnosti" jako
List<string> a vypíše ho přes EnumerateArray. Nebo ať soubor upraví ručně
a znovu spustí jen čtecí část.
Otázka na závěr cvičení: „Proč je lepší mít příšery v souboru než v kódu?" Odpověď: data se dají měnit bez zásahu do programu — i někým, kdo neumí programovat.
dotnet run.Zadání:
Tohle je to, kvůli čemu se celá hodina děje. Hra, která si tě pamatuje.
- Napiš metodu
NovaHra(), která vrátí výchozí stav včetně klíče"verze"💡 Poradit
Metoda vrací slovník:Dictionary<string, object> NovaHra() { return new Dictionary<string, object> { ["verze"] = VERZE, ["jmeno"] = "Arkon", … }; }
Klíč"verze"je nejdůležitější — díky němu poznáš staré uložené hry.- Napiš metodu
Uloz(stav), která stav zapíše jako JSON💡 Poradit
void Uloz(Dictionary<string, object> stav) { … }
Uvnitř:string text = JsonSerializer.Serialize(stav, new JsonSerializerOptions { WriteIndented = true });
aFile.WriteAllText(CESTA, text);
Na konec přidej hlášku, ať víš, že se uložilo.- Když soubor neexistuje, založ novou hru; jinak ho načti
💡 Poradit
if (!File.Exists(CESTA)) { Uloz(NovaHra()); }
Teprve potom čti:var stav = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(File.ReadAllText(CESTA)) ?? new Dictionary<string, JsonElement>();- Zkontroluj verzi a při neshodě založ hru novou
💡 Poradit
Nejdřív se zeptej, jestli klíč vůbec existuje:if (!stav.ContainsKey("verze") || stav["verze"].GetInt32() != VERZE)
Uvnitř zavolejUloz(NovaHra());a soubor načti znovu.
Bez téhle pojistky se hra rozbije, jakmile změníš strukturu uložené hry.- Odehraj jeden tah: úroveň nahoru, zlato, nový předmět — a ulož
💡 Poradit
Nejdřív si vytáhni, co v uložené hře je (GetString,GetInt32,EnumerateArray).
Inventář přelij do obyčejného seznamu:var inventar = new List<string>(); foreach (JsonElement p in stav["inventar"].EnumerateArray()) inventar.Add(p.GetString() ?? "");
Pak přidej předmět, poskládej nový slovník a zavolejUloz.
| Úroveň | Co má zvládnout |
|---|---|
| Minimum | Body 1–3, hra se založí a načte |
| Rozšíření | Bod 4, kontrola verze |
| Výzva | Bod 5 — po každém spuštění hrdina roste a mění se i soubor |
dotnet run.Očekávaný výsledek: při prvním spuštění se založí nová hra (úroveň 1), při druhém už hrdina začíná na úrovni 2 a s 120 zlaťáky. Nechte je spustit program třikrát za sebou — ten rostoucí hrdina je odměna za celou hodinu.
Kde se zaseknou:
Deserialize nad if (!File.Exists…),
spadne to při prvním spuštění. Pořadí je zásadní.Deserialize po obnově verze. Proměnná stav
pak drží stará data. Projeví se to divnými čísly.List<string>. JsonElement nejde jen tak
přidat do nového slovníku — potřebují obyčejný seznam textů.zivoty / 10 do záporu
a new string('█', záporné) spadne. Krásná příležitost pro Math.Max(0, …) —
ať na to přijdou sami tím, že program spustí desetkrát.Kdo je hotový dřív: ať přidá záložní kopii (csquest_save.bak.json)
před každým uložením, nebo ať do stavu přidá klíč "pocet_spusteni",
který se s každým během zvýší o jedna.
Otázka na závěr cvičení: „Proč se do uložené hry ukládá i číslo verze?" Odpověď: aby program poznal soubor ze staré verze a nespadl na chybějícím klíči.
dotnet run.Vyrob program „Kronika tvé postavy", který si pamatuje, co se dělo:
- při každém spuštění načte
kronika.json(a když neexistuje, založí ho),- přidá nový záznam s číslem spuštění, datem a nějakou událostí,
- celou kroniku uloží zpátky do souboru,
- vypíše všechny dosavadní záznamy očíslované pod sebou,
- a na konci napíše, kolikrát už jsi program spustil.
Pošli
Program.csi výslednýkronika.jsonpo nejméně třech spuštěních. Ať je na něm vidět, že si program opravdu pamatuje.
Tenhle úkol se nedá udělat v prohlížeči — potřebuje skutečný soubor na disku.
Je to schválně: cílem je, aby si dítě doma otevřelo VS Code, spustilo dotnet run
třikrát za sebou a vidělo, jak soubor roste. Ten zážitek „program si to pamatuje
i po vypnutí" je pointa celé hodiny.
Kdo nemá doma .NET, ať úkol udělá ve škole o přestávce nebo na konci hodiny —
stačí pět minut. Případně mu zadejte variantu bez souboru: kronika jen v List<string>
a na konci výpis. Pointa se ztratí, ale procvičí si JSON.
Na začátku příští hodiny nechte dva tři žáky ukázat jejich kronika.json
v textovém editoru. Když třída uvidí ten samý text, který program napsal,
pochopí, že JSON není magie, ale obyčejný soubor.
Toto je jedna z možných variant, jak mohl úkol dopadnout. Tvoje řešení se může lišit a to je v pořádku.
dotnet run.Všechno, co jsme dnes použili, je popsané na Microsoft Learn. Odkazy jsou anglicky — klidně si je nech přeložit prohlížečem, kód je stejný v každém jazyce.
| Téma | Odkaz |
|---|---|
Třída File |
learn.microsoft.com — File Class |
File.WriteAllText |
learn.microsoft.com — File.WriteAllText |
File.ReadAllLines |
learn.microsoft.com — File.ReadAllLines |
Directory a Path |
learn.microsoft.com — Path.Combine |
| JSON v .NETu | learn.microsoft.com — JSON serialization overview |
JsonSerializer |
learn.microsoft.com — JsonSerializer Class |
JsonElement |
learn.microsoft.com — JsonElement Struct |
Dneškem se z tvých programů staly aplikace. Rozdíl je jediný, zato zásadní: aplikace si pamatuje, co se stalo.
WriteAllText
od AppendAllText.File.Exists, než začneš číst.JsonSerializer.Serialize a Deserialize a rozumíš tomu,
že jde o cestu tam a zpátky mezi pamětí a diskem.Podívej se na to takhle: každá aplikace v tvém mobilu dělá přesně tohle. Ukládá si nastavení, postup ve hře, rozepsané zprávy. Formát je většinou JSON a metody se jmenují skoro stejně. Dneska jsi otevřel dveře, za kterými programy přestávají být hračkou na jedno spuštění.
Příště začneme skládat CsQuest dohromady — mapu z dvanácté hodiny, seřazený inventář ze třinácté a uloženou hru z dnešní.