JSON i walidacja danych w konsoli
Dane mogą wyglądać poprawnie, a mimo to nie nadawać się do programu. Oddziel odczyt tekstu, sprawdzenie kształtu i sprawdzenie zasad.
Po tej lekcji potrafisz
- Użyjesz JSON.stringify i JSON.parse.
- Sprawdzisz obiekt i tablicę nieznanych danych.
- Odrzucisz pusty tytuł, zły typ oraz powtórzone identyfikatory.
Import listy zadań od innej osoby
Kolega przesyła zadania jako tekst. Nie wiesz, czy jego program używa tych samych pól i typów. Nawet jeśli tekst jest poprawnym JSON-em, pole gotowe może zawierać napis zamiast boolean. Bez walidacji ta pomyłka przejdzie do logiki aplikacji.
Małe kroki, konkretne efekty
JSON to tekstowy zapis danych
stringify zamienia obsługiwane dane na tekst, parse odczytuje tekst. W JSON klucze i napisy mają podwójne cudzysłowy. Nie ma komentarzy, funkcji ani wartości undefined.
const zadanie = { id: 1, tytul: "Trening", gotowe: false };
const tekst = JSON.stringify(zadanie);
console.log(tekst);
const dane: unknown = JSON.parse(tekst);
console.log(typeof dane);
{"id":1,"tytul":"Trening","gotowe":false}
object
JSON.parse nie tworzy instancji Twojej klasy i nie sprawdza interfejsu. Data Date po stringify staje się tekstem, a Map i Set wymagają własnego sposobu zapisu. Obiekty z cyklem i bigint wymagają dodatkowej obsługi.
Sprawdzaj po kolei
Najpierw obiekt i null, potem obecność pól, następnie ich rodzaje i wartości. Kolejność && ma znaczenie: prawa strona wykona się dopiero, gdy lewa jest prawdziwa.
interface Gracz { nick: string; punkty: number }
function czyGracz(x: unknown): x is Gracz {
if (typeof x !== "object" || x === null) { return false; }
return "nick" in x && typeof x.nick === "string" && x.nick.trim().length > 0
&& "punkty" in x && typeof x.punkty === "number" && Number.isFinite(x.punkty) && x.punkty >= 0;
}
console.log(czyGracz({ nick: "Ala", punkty: 5 }));
console.log(czyGracz({ nick: "Ala", punkty: "5" }));
console.log(czyGracz(null));
true
false
false
Predykat x is Gracz jest zobowiązaniem autora. Tutaj sprawdzamy wszystkie wymagane pola. Akceptujemy dodatkowe pola, ale nie musimy przenosić ich do docelowego modelu.
Import zadań z kontrolą identyfikatorów
To projekt z dwoma plikami. model.ts sprawdza pojedyncze zadania i całą listę. index.ts pokazuje poprawny import oraz powtórzone id. Wszystko działa na tekstach w konsoli.
export interface Zadanie { id: number; tytul: string; gotowe: boolean }
export function czyZadanie(x: unknown): x is Zadanie {
if (typeof x !== "object" || x === null) { return false; }
return "id" in x && typeof x.id === "number" && Number.isSafeInteger(x.id) && x.id > 0
&& "tytul" in x && typeof x.tytul === "string" && x.tytul.trim().length > 0
&& "gotowe" in x && typeof x.gotowe === "boolean";
}
export function odczytaj(tekst: string): Zadanie[] {
const dane: unknown = JSON.parse(tekst);
if (!Array.isArray(dane) || !dane.every(czyZadanie)) { throw new Error("Niepoprawne zadania"); }
const ids = new Set<number>();
const wynik: Zadanie[] = [];
for (const z of dane) {
if (ids.has(z.id)) { throw new Error("Powtórzone id"); }
ids.add(z.id);
wynik.push({ id: z.id, tytul: z.tytul.trim(), gotowe: z.gotowe });
}
return wynik;
}
import { odczytaj } from "./model.js";
const poprawne = '[{"id":1,"tytul":" Trening ","gotowe":false}]';
const powtorzone = '[{"id":1,"tytul":"A","gotowe":false},{"id":1,"tytul":"B","gotowe":true}]';
for (const tekst of [poprawne, powtorzone]) {
try { console.log(JSON.stringify(odczytaj(tekst))); }
catch (e) { console.log(e instanceof Error ? e.message : "Błąd importu"); }
}
[{"id":1,"tytul":"Trening","gotowe":false}]
Powtórzone id
Jak program dochodzi do wyniku
- Array.isArray odróżnia listę od pojedynczego obiektu.
- every(czyZadanie) sprawdza każdy element; pusta lista jest poprawna według przyjętej umowy.
- Set wykrywa powtórzenia id, których sam interfejs nie opisuje.
- Tworzymy nowe obiekty z dokładnie trzema polami i przycinamy tytuł. Dodatkowe pola wejścia nie trafiają do wyniku.
Zanim przejdziesz dalej
Czy [{„id”:1,”tytul”:”A”,”gotowe”:”false”}] przejdzie sprawdzanie?
Sprawdź swoje rozumowanie
Nie. Napis „false” nie jest wartością false. Co więcej, Boolean(„false”) daje true, więc automatyczna konwersja byłaby tutaj błędem.
Zmień kod i zobacz, co się stanie
Testuj także poprawny JSON o złym kształcie
Błędna składnia to tylko jeden rodzaj problemu. null, liczba i pusty obiekt to poprawny JSON, który może nie spełniać wymagań Twojego programu.
const teksty = ["null", "42", "{}", "[]"];
for (const tekst of teksty) {
const x: unknown = JSON.parse(tekst);
console.log(tekst + ": tablica=" + Array.isArray(x));
}
null: tablica=false
42: tablica=false
{}: tablica=false
[]: tablica=true
Nie używaj JSON.parse(tekst) as Zadanie[] jako skrótu walidacji. Kompilator uwierzy, ale program nie wykona ani jednego dodatkowego sprawdzenia.
Gdy wynik zaskakuje
| Pomyłka | Co się dzieje i jak to poprawić |
|---|---|
| JSON.parse traktowane jako walidacja modelu | parse sprawdza składnię JSON. Kształt i zasady sprawdzasz sam. |
| Boolean(„false”) do naprawiania danych | Każdy niepusty tekst jest truthy, więc wynik to true. |
| Pomijanie duplikatów id | Dwa zadania mogą wtedy reagować na tę samą komendę. |
Teraz Twoja kolej
Rozwiązanie jest jedną z możliwości. Zanim je odkryjesz, uruchom własną wersję dla podanych danych i przynajmniej jednego przypadku brzegowego.
Zamień { muzyka: true, glosnosc: 0 } na JSON i wypisz. Wyjaśnij, czemu zero nie zniknęło.
Mała podpowiedź
JSON obsługuje liczbę 0.
Przykładowe rozwiązanie z wyjaśnieniem
console.log(JSON.stringify({ muzyka: true, glosnosc: 0 }));
{"muzyka":true,"glosnosc":0}
Zero to wartość danych, nie brak pola.
Przyjmij unknown i sprawdź, czy to tablica samych niepustych tekstów po trim. Pusta lista jest dozwolona. Sprawdź [„A”], [” „], [1], [].
Mała podpowiedź
Array.isArray i every z typeof.
Przykładowe rozwiązanie z wyjaśnieniem
function nicki(x: unknown): x is string[] {
return Array.isArray(x) && x.every(n => typeof n === "string" && n.trim().length > 0);
}
for (const x of [["A"], [" "], [1], []]) { console.log(nicki(x)); }
true
false
false
true
Zasada dla pustej listy jest jawna; every nie wymaga istnienia elementu.
Z obiektu z nickiem, punktami i sekretem utwórz nową publiczną kartę zawierającą tylko nick. Nie używaj as do usunięcia sekretu.
Mała podpowiedź
Zbuduj literał z wybranym polem.
Przykładowe rozwiązanie z wyjaśnieniem
const profil = { nick: "Ala", punkty: 12, sekret: "testowy" };
const karta = { nick: profil.nick };
console.log(JSON.stringify(karta));
{"nick":"Ala"}
Fizycznie tworzysz nowy obiekt bez pozostałych właściwości.
Odczytaj JSON i przyjmij tylko tablicę skończonych liczb. Dla [1,2] wypisz sumę 3, dla [1,”2″] wypisz „Błędne liczby”.
Mała podpowiedź
Odczyt unknown, Array.isArray i every; dopiero potem reduce.
Przykładowe rozwiązanie z wyjaśnieniem
function suma(tekst: string): number {
const x: unknown = JSON.parse(tekst);
if (!Array.isArray(x) || !x.every((n: unknown): n is number => typeof n === "number" && Number.isFinite(n))) {
throw new Error("Błędne liczby");
}
return x.reduce((s, n) => s + n, 0);
}
for (const tekst of ["[1,2]", '[1,"2"]']) {
try { console.log(suma(tekst)); }
catch (e) { if (e instanceof Error) { console.log(e.message); } }
}
3
Błędne liczby
Błędny element nie jest automatycznie zamieniany na liczbę.
Co zabierasz do następnej lekcji
- Poprawna składnia JSON nie oznacza poprawnego modelu.
- Waliduj rodzaj, kształt i reguły danych oddzielnie.
- Po walidacji twórz przewidywalny model z potrzebnych pól.