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.

CEL LEKCJI

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.
WPROWADZENIE

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.

POZNAJ SKŁADNIĘ

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.

src/index.ts — osobny przykład
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);
wynik w konsoli
{"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.

src/index.ts — osobny przykład
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));
wynik w konsoli
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.

PRZYKŁAD Z OMÓWIENIEM

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.

src/model.ts — plik tego projektu
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;
}
src/index.ts — osobny przykład
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"); }
}
wynik w konsoli
[{"id":1,"tytul":"Trening","gotowe":false}]
Powtórzone id

Jak program dochodzi do wyniku

  1. Array.isArray odróżnia listę od pojedynczego obiektu.
  2. every(czyZadanie) sprawdza każdy element; pusta lista jest poprawna według przyjętej umowy.
  3. Set wykrywa powtórzenia id, których sam interfejs nie opisuje.
  4. 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.

EKSPERYMENT

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.

src/index.ts — osobny przykład
const teksty = ["null", "42", "{}", "[]"];
for (const tekst of teksty) {
  const x: unknown = JSON.parse(tekst);
  console.log(tekst + ": tablica=" + Array.isArray(x));
}
wynik w konsoli
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.

CZĘSTE BŁĘDY

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ę.
ZADANIA

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.

ZAD 1Zapis konfiguracji★☆☆

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
src/index.ts — osobny przykład
console.log(JSON.stringify({ muzyka: true, glosnosc: 0 }));
wynik w konsoli
{"muzyka":true,"glosnosc":0}

Zero to wartość danych, nie brak pola.

ZAD 2Strażnik listy nicków★★☆

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
src/index.ts — osobny przykład
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)); }
wynik w konsoli
true
false
false
true

Zasada dla pustej listy jest jawna; every nie wymaga istnienia elementu.

ZAD 3Usuń dodatkowe pola★★☆

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
src/index.ts — osobny przykład
const profil = { nick: "Ala", punkty: 12, sekret: "testowy" };
const karta = { nick: profil.nick };
console.log(JSON.stringify(karta));
wynik w konsoli
{"nick":"Ala"}

Fizycznie tworzysz nowy obiekt bez pozostałych właściwości.

ZAD 4Import liczb z komunikatem★★★

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
src/index.ts — osobny przykład
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); } }
}
wynik w konsoli
3
Błędne liczby

Błędny element nie jest automatycznie zamieniany na liczbę.

PODSUMOWANIE

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.