Parsowanie JSON-a w JavaScript to coś, co będziesz robić setki razy jako web developer. Na szczęście JavaScript ma do tego wbudowane wsparcie — bez żadnych bibliotek. Jest jednak więcej niuansów dotyczących JSON.parse() i JSON.stringify() niż sugeruje dokumentacja. Przejdźmy przez to porządnie.

JSON.parse() — zamiana stringa na obiekt

JSON.parse() przyjmuje string w formacie JSON i konwertuje go na wartość JavaScript. Ta wartość to zazwyczaj obiekt lub tablica, ale może być też stringiem, liczbą, boolanem lub null:

js
const jsonString = '{"name": "Alice", "age": 30, "active": true}';
const user = JSON.parse(jsonString);

console.log(user.name);    // "Alice"
console.log(user.age);     // 30
console.log(user.active);  // true
console.log(typeof user);  // "object"

Pamiętaj, że na wejście musi trafić string. Częsty błąd to próba parsowania wartości, która już jest obiektem — JSON.parse({}) rzuci SyntaxError, bo najpierw konwertuje obiekt na "[object Object]", a to nie jest poprawny JSON.

Zawsze owijaj JSON.parse() w try/catch

Oto coś, o czym nikt ci nie mówi: JSON.parse() rzuca SyntaxError, gdy wejście nie jest poprawnym JSON-em. Jeśli parsujesz dane z API, wejścia użytkownika lub pliku, zawsze powinieneś obsłużyć ten błąd. Jedna zniekształcona odpowiedź może crashnąć aplikację, jeśli tego nie zrobisz:

js
function safeParseJSON(str) {
  try {
    return { data: JSON.parse(str), error: null };
  } catch (err) {
    return { data: null, error: err.message };
  }
}

const { data, error } = safeParseJSON('{"name": "Alice"}');
if (error) {
  console.error('Invalid JSON:', error);
} else {
  console.log(data.name); // "Alice"
}

// Handles bad input gracefully
const result = safeParseJSON('not json at all');
console.log(result.error); // "Unexpected token 'o', "not json "... is not valid JSON"

Używam takiego wrappera w prawie każdym projekcie. Sprawia, że obsługa błędów jest spójna i zapobiega brzydkim nieprzechwyconym wyjątkom, które docierają do UI.

JSON.stringify() — zamiana obiektu na string

JSON.stringify() robi coś odwrotnego: konwertuje wartość JavaScript na string JSON. Używasz tego przy wysyłaniu danych do API, zapisywaniu do localStorage lub pisaniu do pliku:

js
const user = {
  name: "Bob",
  age: 25,
  roles: ["admin", "editor"],
  password: "secret123" // we'll handle this later
};

// Basic usage
const jsonString = JSON.stringify(user);
// '{"name":"Bob","age":25,"roles":["admin","editor"],"password":"secret123"}'

// Pretty-printed (great for logs and file output)
const prettyJson = JSON.stringify(user, null, 2);
console.log(prettyJson);
// {
//   "name": "Bob",
//   "age": 25,
//   "roles": ["admin", "editor"],
//   "password": "secret123"
// }

Trzeci argument JSON.stringify() to poziom wcięcia. Użycie 2 lub 4 daje czytelny wynik. Domyślnie (bez trzeciego argumentu) dostajesz zwarty, zminifikowany JSON — lepszy do transmisji przez sieć.

Co stringify() po cichu pomija

To jedna z pułapek, na którą regularnie wpadają deweloperzy. JSON.stringify() po cichu pomija pewne wartości, bo JSON ich nie obsługuje:

js
const data = {
  name: "Alice",
  greet: function() { return "hi"; },   // Functions → dropped
  undef: undefined,                      // undefined → dropped
  sym: Symbol("key"),                    // Symbols → dropped
  nan: NaN,                              // NaN → null
  inf: Infinity,                         // Infinity → null
  date: new Date("2024-01-15")           // Dates → ISO string
};

console.log(JSON.stringify(data, null, 2));
// {
//   "name": "Alice",
//   "nan": null,
//   "inf": null,
//   "date": "2024-01-15T00:00:00.000Z"
// }
// greet, undef, sym are GONE
Uwaga: Jeśli zserializujesz obiekt z wartościami undefined, a potem go sparsujemy, te klucze zupełnie znikną. Może to powodować subtelne bugi, jeśli twój kod sprawdza później obj.key === undefined.

Pobieranie JSON z API — wzorzec z prawdziwego życia

W praktyce większość parsowania JSON-a dzieje się podczas pobierania danych z API. Fetch API sprawia, że to proste — response.json() obsługuje parsowanie za ciebie:

js
async function getUser(userId) {
  try {
    const response = await fetch(`https://api.example.com/users/${userId}`);

    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`);
    }

    const user = await response.json(); // parses JSON automatically
    return user;
  } catch (err) {
    console.error('Failed to fetch user:', err);
    return null;
  }
}

const user = await getUser(42);
if (user) {
  console.log(`Hello, ${user.name}!`);
}

response.json() to w zasadzie JSON.parse(await response.text()). Też rzuca błąd, jeśli ciało odpowiedzi nie jest poprawnym JSON-em, więc warto obsłużyć to osobno w kodzie produkcyjnym.

Praca z zagnieżdżonymi danymi JSON

Głęboko zagnieżdżony JSON jest powszechny w prawdziwych API. Oto jak bezpiecznie się po nim poruszać, nie crashując przy brakujących kluczach:

js
const apiResponse = {
  "user": {
    "profile": {
      "address": {
        "city": "Berlin"
      }
    }
  }
};

// Unsafe — throws if any level is undefined
const city1 = apiResponse.user.profile.address.city; // "Berlin"

// Safe with optional chaining (ES2020+) — returns undefined instead of throwing
const city2 = apiResponse?.user?.profile?.address?.city; // "Berlin"
const zip   = apiResponse?.user?.profile?.address?.zip;  // undefined (not a crash)

// With a fallback using nullish coalescing
const country = apiResponse?.user?.profile?.address?.country ?? "Unknown";

Optional chaining (?.) oraz nullish coalescing (??) to nowoczesne funkcje JavaScript — obie część specyfikacji ECMAScript od 2020 roku — które sprawiają, że praca z niepewnymi strukturami JSON jest znacznie bezpieczniejsza. Używaj ich swobodnie — są obsługiwane przez wszystkie nowoczesne przeglądarki i Node.js 14+.

Funkcje replacer i reviver

Zarówno JSON.stringify(), jak i JSON.parse() przyjmują drugi argument, który pozwala dostosować transformację. Są naprawdę przydatne w praktycznych scenariuszach:

js
// replacer: filter or transform values during stringify
const user = { name: "Alice", password: "s3cr3t", age: 30 };
const safe = JSON.stringify(user, ["name", "age"]); // only include these keys
// '{"name":"Alice","age":30}'  — password is excluded

// reviver: transform values during parse
const dateJson = '{"name": "Alice", "createdAt": "2024-01-15T09:30:00Z"}';
const parsed = JSON.parse(dateJson, (key, value) => {
  if (key === "createdAt") return new Date(value); // convert string to Date
  return value;
});
console.log(parsed.createdAt instanceof Date); // true
console.log(parsed.createdAt.getFullYear());   // 2024

Wzorzec reviver jest szczególnie przydatny do przywracania obiektów Date po rundzie przez JSON, bo JSON nie ma natywnego typu daty.

Przydatne narzędzia

Podczas pracy z JSON-em w projektach JavaScript te narzędzia oszczędzają czas: JSON Formatter do pretty-printowania zminifikowanych odpowiedzi API, JSON Validator do sprawdzania błędów składni, JSON Path do odpytywania konkretnych pól z dużych payloadów oraz JSON Escape, gdy musisz osadzić JSON w stringu. Więcej szczegółów znajdziesz w dokumentacji JSON na MDN — jest doskonała.

Podsumowanie

JSON.parse() i JSON.stringify() to dwie funkcje, których potrzebujesz. Kluczowe nawyki do wyrobienia: zawsze owijaj parse() w try/catch, używaj optional chaining do dostępu do zagnieżdżonych danych i wiedz, co stringify() po cichu pomija. Opanuj te trzy rzeczy i poradzisz sobie z 99% realnych scenariuszy JSON-owych bez niespodzianek.