Ten obszerny przewodnik pokazuje, jak skutecznie pracować z danymi JSON w JavaScript — od podstaw po praktyczne, zaawansowane techniki. Nauczysz się parsować tekst JSON do obiektów, serializować obiekty do JSON, bezpiecznie przesyłać dane przez sieć i je walidować. JSON jest dziś podstawowym formatem wymiany danych w aplikacjach webowych, a jego dobra znajomość znacząco przyspiesza pracę i poprawia niezawodność systemów.
Podstawy i struktura JSON
JSON, czyli JavaScript Object Notation, to lekki, tekstowy format przechowywania i transportu danych. Jest czytelny dla człowieka i łatwy do przetworzenia przez maszyny. W porównaniu z XML JSON ma prostszą składnię i mniejszy narzut, dlatego stał się de facto standardem dla współczesnych API.
Rdzeniem JSON są pary klucz–wartość oraz tablice. Klucze zawsze są ciągami w podwójnych cudzysłowach, po których następuje dwukropek i odpowiadająca im wartość. Z tych prostych klocków zbudujesz złożone, zagnieżdżone struktury wiernie odwzorowujące relacje danych.
Oto typy wartości wspierane przez specyfikację JSON:
- ciągi znaków – zawsze w podwójnych cudzysłowach; znaki specjalne (np. cudzysłów) należy „uciekać” ukośnikiem;
- liczby – całkowite i zmiennoprzecinkowe w notacji dziesiętnej bez wiodących zer;
- boolean – wartości
trueifalsezapisane małymi literami; - null – słowo kluczowe
nulloznaczające świadomy brak wartości; - tablice – uporządkowane listy w nawiasach kwadratowych, mogą zawierać mieszane typy;
- obiekty – nieuporządkowane zbiory par klucz–wartość w nawiasach klamrowych.
Warto pamiętać o ograniczeniach JSON:
- funkcje – nie są dozwolone, JSON przenosi wyłącznie dane;
- undefined – nie ma reprezentacji, właściwości o takiej wartości są pomijane przy serializacji;
- Date – nie ma typu daty, daty zapisuj jako ciągi (najlepiej ISO 8601) i konwertuj przy odczycie.
Najważniejsze reguły poprawnej składni JSON to:
- brak przecinków końcowych – ostatni element w obiekcie/tablicy nie kończy się przecinkiem;
- właściwe nawiasy – obiekty w nawiasach klamrowych
{}, tablice w nawiasach kwadratowych[]; - podwójne cudzysłowy – wszystkie ciągi i klucze obiektów w
"...", nie w'...'; - rozszerzenie i nagłówek – pliki
.json, a w HTTP nagłówekContent-Type: application/json.
Parsowanie JSON – deserializacja i konwersja ciągów na obiekty
Parsowanie JSON konwertuje tekst w formacie JSON do obiektu lub tablicy JavaScript. Metoda JSON.parse() zamienia ciąg JSON w strukturę JS gotową do dalszego przetwarzania.
Przykład podstawowy: const obj = JSON.parse('{"name":"John","age":30}'); Po sparsowaniu odczytasz dane jak zwykle: obj.name zwróci "John", a obj.age wartość 30. Gdy na najwyższym poziomie masz tablicę, JSON.parse() zwróci tablicę JS, np. z '["Ford","BMW"]' otrzymasz zwykłą tablicę do iteracji.
Drugim argumentem JSON.parse() jest funkcja reviver, która pozwala modyfikować wartości w trakcie parsowania (np. automatycznie konwertować daty): JSON.parse(text, (key, value) => key === 'birth' ? new Date(value) : value). Dzięki reviverowi zrealizujesz transformacje „w locie”, bez dodatkowego etapu po parsowaniu.
Aby uniknąć przerwania działania aplikacji przez nieprawidłowy JSON, stosuj try...catch i waliduj dane przed użyciem.
Najczęstsze źródła błędów parsowania to:
- brak cudzysłowów wokół kluczy – klucze zawsze w podwójnych cudzysłowach;
- przecinki końcowe – ostatni element nie może kończyć się przecinkiem;
- pojedyncze cudzysłowy – JSON wymaga podwójnych cudzysłowów;
- niezrównoważone nawiasy – brakujące lub nadmiarowe nawiasy
{[]}; - znaki specjalne bez „ucieczki” – np. surowe nowe linie w ciągach bez
\n.
Tworzenie JSON – serializacja i konwersja obiektów na ciągi
Serializacja zamienia obiekty JS na tekst JSON do wysyłki lub zapisu. JSON.stringify() jest niezbędne zawsze, gdy dane opuszczają środowisko JS (np. sieć, localStorage).
Przykład: JSON.stringify({name:"Sara",age:25,department:"IT"}) zwróci '{"name":"Sara","age":25,"department":"IT"}'.
Najważniejsze możliwości JSON.stringify(value, replacer, space) to:
- space – kontrola wcięć dla czytelności, np.
JSON.stringify(obj, null, 2); - replacer (funkcja lub tablica) – precyzyjna kontrola, które właściwości i jak są zapisywane;
- obsługa cykli – własny
replacermoże wykrywać i usuwać referencje cykliczne (inaczej otrzymaszTypeError).
Pretty printing ułatwia debugowanie (space), natomiast w produkcji preferuj JSON zwarty dla mniejszego rozmiaru.
Przesyłanie danych JSON przez sieć
Fetch API zapewnia nowoczesny, obietnicowy interfejs do żądań HTTP. W połączeniu z async/await kod jest prosty i czytelny: const res = await fetch('/api/data'); const data = await res.json();
Wysyłając JSON metodą POST, ustaw poprawne nagłówki i zserializuj ciało: fetch('/api/users',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}). Nagłówek Content-Type: application/json informuje serwer, że treść żądania to JSON.
Pamiętaj o różnicy między błędami sieci a statusami HTTP — fetch odrzuca obietnicę tylko przy błędzie transportu. Sprawdzaj response.ok lub response.status i stosuj try...catch wokół wywołań i parsowania.
Dobre praktyki przy komunikacji z API:
- sprawdzaj nagłówki – dla wysyłki
Content-Type: application/json, dla odbioruAccept: application/json; - weryfikuj
response.ok– ręcznie zgłaszaj błąd dla statusów 4xx/5xx; - otocz parsowanie blokiem
try...catch– obsłuż zarówno błędy sieci, jak i błędy formatu JSON; - uwzględnij timeouty i retry – aby zwiększyć odporność na fluktuacje sieci.
W projektach legacy możesz spotkać XMLHttpRequest; daje więcej kontroli na niskim poziomie, ale jest bardziej rozbudowany od Fetch.
Przechowywanie i trwałość danych z JSON
localStorage i sessionStorage przechowują wyłącznie ciągi, więc obiekty należy serializować (JSON.stringify) i parsować przy odczycie (JSON.parse). To prosta i szybka pamięć po stronie przeglądarki.
Poniżej zestawienie kluczowych różnic między mechanizmami:
| Właściwość | localStorage | sessionStorage |
|---|---|---|
| Trwałość | Przetrwa restart przeglądarki | Wygasa po zamknięciu karty/okna |
| Zakres | Domena + protokół + port | Bieżąca karta/zakładka |
| Zastosowania | preferencje, cache, dane długowieczne | stan formularzy, dane tymczasowe |
| Bezpieczeństwo | narażone na XSS (unikaj wrażliwych tokenów) | narażone na XSS (unikaj wrażliwych tokenów) |
Przykłady użycia: localStorage.setItem('user', JSON.stringify(userData)) oraz const user = JSON.parse(localStorage.getItem('user')). Dla kolekcji obiektów możesz stosować „namespacing” kluczy, np. todos:1, todos:2, aby łatwo filtrować i aktualizować fragmenty danych.
Zaawansowane techniki JSON i złożone operacje
Dla głębokich struktur korzystaj z notacji kropkowej lub nawiasowej oraz z opcjonalnego łańcuchowania ?., aby unikać wyjątków przy brakujących polach: const email = user?.contact?.email;
Funkcje rekurencyjne pozwalają przejść i przetworzyć dowolnie zagnieżdżone drzewo obiektu.
Głębokie kopiowanie bywa realizowane wzorcem JSON.parse(JSON.stringify(obj)), ale ma istotne ograniczenia:
- utrata funkcji – funkcje nie są serializowane;
- utrata
undefinedi symboli – te wartości nie mają reprezentacji w JSON; - utrata semantyki dat – obiekty
Datestają się zwykłym ciągiem; - brak obsługi cykli – serializacja przerwie się błędem.
Nowe structuredClone() lepiej odwzorowuje typy i prawidłowo obsługuje referencje cykliczne.
Strategie radzenia sobie z referencjami cyklicznymi przy serializacji:
- usuwanie cykli – filtrowanie pól powodujących zapętlenie w
replacer; - referencje przez ID – przechowuj identyfikatory zamiast pełnych obiektów;
- spłaszczanie grafu – rozbij strukturę na słowniki/indeksy obiektów.
Walidacja i zgodność ze schematem z użyciem JSON Schema (np. biblioteka Ajv) pozwala wcześnie wykryć błędy formatu i poprawić integralność danych.
Praca z tablicami i filtrowanie danych
Metody tablicowe znakomicie współgrają z danymi JSON. Oto najważniejsze narzędzia:
- map() – transformuje każdy element i zwraca nową tablicę, np.
products.map(p => p.price); - filter() – wybiera elementy spełniające warunek, np.
products.filter(p => p.price > 100); - reduce() – akumuluje wartości, np.
items.reduce((sum, i) => sum + i.price, 0).
Łańcuchy map/filter/reduce pozwalają w jednym wyrażeniu filtrować, transformować i sortować dane: data.filter(i => i.active).map(i => i.name).sort().
Obsługa błędów i walidacja danych
Solidna obsługa błędów jest kluczowa przy danych z niepewnych źródeł. Zawsze otaczaj parsowanie try...catch i loguj szczegóły błędu (SyntaxError wskaże miejsce problemu).
W warstwie sieciowej sprawdzaj, czy odpowiedź faktycznie zawiera JSON (nagłówek Content-Type), zanim wywołasz response.json().
Warstwy bezpiecznej walidacji danych po parsowaniu:
- weryfikacja typu – sprawdź podstawowe typy i obecność wymaganych pól;
- weryfikacja kształtu – użyj schematów (JSON Schema/Ajv) dla złożonych struktur;
- weryfikacja domenowa – reguły biznesowe (np. zakresy, zależności pól).
Zastosowania w praktyce i przykłady
Gdzie najczęściej wykorzystasz JSON w codziennej pracy:
- REST API – żądania/odpowiedzi w JSON, poprawne kody statusu i spójny model błędów;
- paginacja i filtrowanie – zwracanie danych razem z metadanymi nawigacyjnymi, np.
{data, page, totalPages, links}; - pliki konfiguracyjne –
package.jsonw Node.js, konfiguracje narzędzi CI/CD, Dockera i Kubernetesa; - migracje i ETL – odczyt, transformacja i zapis danych między systemami w formacie JSON.
Współczesne wzorce JavaScript dla JSON
Destrukturyzacja upraszcza dostęp do zagnieżdżonych pól: const {name, address:{city}} = userData;
Funkcje strzałkowe świetnie łączą się z metodami tablicowymi: data.map(i => ({id:i.id, label:i.name})).
Async/await poprawia czytelność kodu asynchronicznego: const data = await response.json();