Klasa `JSON` w JavaScript

Klasa `JSON` w JavaScript

Ten artykuł wyjaśnia klasę JSON w JavaScript.

Omówimy klasę JSON w JavaScript na praktycznych przykładach.

YouTube Video

Klasa JSON w JavaScript

Obiekt JSON posiada głównie dwie metody. JSON.stringify() konwertuje obiekt na łańcuch znaków JSON, a JSON.parse() tworzy obiekt z łańcucha JSON. JSON to format wymiany danych, który może wyrażać tylko niektóre wartości JavaScript. Funkcje, undefined, Symbol oraz odniesienia cykliczne nie mogą być bezpośrednio konwertowane do JSON.

JSON.stringify()

Najpierw podstawowy przykład konwersji obiektu na łańcuch znaków JSON.

1// Convert a JavaScript object to a JSON string.
2const obj = { name: "Alice", age: 30, active: true };
3const jsonString = JSON.stringify(obj);
4console.log(jsonString); // {"name":"Alice","age":30,"active":true}
  • Ten kod to najprostszy przykład konwersji zwykłego obiektu na łańcuch JSON. JSON.stringify zwraca łańcuch znaków synchronicznie.

Drugi argument JSON.stringify (replacer)

Określając replacer, czyli drugi argument JSON.stringify, możesz precyzyjnie kontrolować zasady konwersji. Oto przykład użycia tablicy, aby zachować tylko wybrane właściwości.

1// Use a replacer array to include only specific properties during stringification.
2const user = { id: 1, name: "Bob", password: "secret", role: "admin" };
3const safeJson = JSON.stringify(user, ["id", "name", "role"]);
4console.log(safeJson); // {"id":1,"name":"Bob","role":"admin"}
  • W tym przykładzie password jest wykluczone, aby umożliwić bezpieczny zapis do logów. Jest to przydatne, gdy chcesz usunąć wrażliwe dane ze względów bezpieczeństwa.

Użycie replacer jako funkcji (do konwersji i filtrowania wartości)

Jeśli potrzebujesz większej elastyczności, możesz przekazać funkcję przetwarzającą każdy klucz i wartość. Poniżej znajduje się przykład zachowania obiektu Date jako łańcucha ISO.

 1// Use a replacer function to customize serialization.
 2const data = { name: "Carol", joined: new Date("2020-01-01T12:00:00Z") };
 3const jsonCustom = JSON.stringify(data, (key, value) => {
 4  // Convert Date objects to ISO strings explicitly
 5  if (this && this[key] instanceof Date) {
 6    return this[key].toISOString();
 7  }
 8  return value;
 9});
10console.log(jsonCustom); // {"name":"Carol","joined":"2020-01-01T12:00:00.000Z"}
  • W funkcji typu replacer przekazywane są klucz i wartość, a ich zwrócona wartość zostaje użyta w końcowym JSON. this odnosi się do obiektu rodzica, więc można go również użyć do zagnieżdżonych konwersji.

Podstawy JSON.parse()

JSON.parse konwertuje łańcuch znaków JSON z powrotem na obiekt. Owiń to w try/catch, aby obsłużyć nieprawidłowy JSON.

1// Parse a JSON string into an object and handle parsing errors.
2const jsonText = '{"title":"Example","count":42}';
3try {
4  const parsed = JSON.parse(jsonText);
5  console.log(parsed.title); // Example
6} catch (err) {
7  console.error("Invalid JSON:", err.message);
8}
  • Jeśli łańcuch jest nieprawidłowy, zostanie zgłoszony wyjątek – zawsze stosuj obsługę wyjątków przy pracy z danymi zewnętrznymi.

Przywracanie danych według własnych zasad przy użyciu reviver (przykład z datą)

Dzięki drugiemu argumentowi reviver w JSON.parse możesz przywracać konkretne wartości. Oto przykład zamiany przechowywanej daty jako tekst na obiekt Date.

 1// Use a reviver to turn ISO date strings back into Date objects.
 2const jsonWithDate = '{"name":"Dana","joined":"2021-06-15T09:00:00.000Z"}';
 3const obj = JSON.parse(jsonWithDate, (key, value) => {
 4  // A simple check for ISO date-like strings
 5  if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(value)) {
 6    return new Date(value);
 7  }
 8  return value;
 9});
10console.log(obj.joined instanceof Date); // true
  • reviver jest wywoływana dla każdego klucza, a zwrócona wartość trafia do końcowego obiektu. Dopasuj format i weryfikację dat zgodnie z potrzebami twojej aplikacji.

Czytelne formatowanie i argument space

Aby otrzymać sformatowany wynik z wcięciami, przekaż liczbę lub łańcuch jako trzeci argument JSON.stringify.

1// Pretty-print an object with 2-space indentation for readability.
2const config = { host: "localhost", port: 3000, debug: true };
3const pretty = JSON.stringify(config, null, 2);
4console.log(pretty);
5/* {
6  "host": "localhost",
7  "port": 3000,
8  "debug": true
9} */
  • Dla JSON-a przeznaczonego do czytania przez ludzi, np. w logach lub plikach konfiguracyjnych, warto dodać wcięcia. Należy jednak pamiętać, że zwiększa to rozmiar danych.

Jak radzić sobie z odniesieniami cyklicznymi

JSON.stringify zgłasza TypeError, jeśli występuje odniesienie cykliczne. Częstym rozwiązaniem jest stworzenie funkcji replacer wykorzystującej zbiór seen do unikania odniesień cyklicznych.

 1// Safely stringify objects that may contain circular references.
 2function safeStringify(value) {
 3  const seen = new WeakSet();
 4  return JSON.stringify(value, (key, val) => {
 5    if (val && typeof val === "object") {
 6      if (seen.has(val)) return "[Circular]";
 7      seen.add(val);
 8    }
 9    return val;
10  });
11}
12
13const a = { name: "A" };
14a.self = a;
15console.log(safeStringify(a)); // {"name":"A","self":"[Circular]"}
  • Używając WeakSet, możesz wykryć odniesienia cykliczne, unikając jednocześnie wycieków pamięci. W przypadku odniesienia cyklicznego zwraca "[Circular]". Możliwe jest także przypisanie identyfikatora odniesienia zamiast "[Circular]".

Własna serializacja przy użyciu metody toJSON

Jeśli zdefiniujesz metodę toJSON na obiekcie, JSON.stringify użyje jej zwróconej wartości. To przydatne, gdy chcesz nadpisać zasady konwersji dla poszczególnych typów.

 1// Define toJSON on a class to customize its JSON representation.
 2class Point {
 3  constructor(x, y) {
 4    this.x = x;
 5    this.y = y;
 6  }
 7  toJSON() {
 8    // This will be used by JSON.stringify
 9    return { x: this.x, y: this.y, type: "Point" };
10  }
11}
12
13const p = new Point(10, 20);
14console.log(JSON.stringify(p)); // {"x":10,"y":20,"type":"Point"}
  • toJSON pozwala definiować zasady serializacji na poziomie obiektu, co jest bardziej lokalne i intuicyjne niż stosowanie replacer.

Ograniczenia i uwagi dotyczące JSON

Przykłady, które nie mogą być konwertowane do JSON to: undefined, funkcje, Symbol, BigInt i odniesienia cykliczne. Należy zwracać uwagę także na precyzję liczb, np. duże liczby całkowite i zaokrąglenia liczb zmiennoprzecinkowych (IEEE).

 1// Demonstrate values that can't be represented in JSON.
 2const sample = {
 3  a: undefined,
 4  b: function () {},
 5  c: Symbol("s"),
 6  d: 9007199254740993n // BigInt (note: JSON.stringify will throw on BigInt)
 7};
 8// JSON.stringify will throw for BigInt and will drop undefined, functions, symbols in objects.
 9try {
10  console.log(JSON.stringify(sample));
11} catch (err) {
12  console.error("Error during stringify:", err.message);
13}
  • Podczas obsługi BigInt przekonwertuj je na łańcuch lub zdefiniuj sposób reprezentacji za pomocą własnego replacer lub toJSON.

Bezpieczeństwo (obsługa nieufanego JSON-a)

JSON.parse sam w sobie jest bezpieczny i uważany za bezpieczniejszy niż eval, ale nie należy ufać sparsowanemu obiektowi i używać go bez sprawdzenia do dostępu do właściwości lub zapytań do bazy danych. Zawsze waliduj i sprawdzaj, czy dane są zgodne z oczekiwanym schematem.

1// Parse external JSON and validate expected properties before use.
2const external = '{"username":"eve","role":"user"}';
3const parsed = JSON.parse(external);
4if (typeof parsed.username === "string" && ["user","admin"].includes(parsed.role)) {
5  console.log("Safe to use parsed.username and parsed.role.");
6} else {
7  throw new Error("Invalid payload");
8}
  • Do walidacji wejścia (walidacja schematu) warto stosować biblioteki takie jak ajv.

Uwagi dotyczące wydajności

Częste serializowanie i deserializowanie dużych obiektów obciąża CPU i pamięć. W razie potrzeby można rozważyć przesyłanie jedynie różnic, zastosowanie formatów binarnych (np. MessagePack) lub strumieniowanie (przetwarzanie danych sekwencyjnie). W środowiskach przeglądarkowych można korzystać z structuredClone (do kopiowania) lub obiektów przenośnych w postMessage.

Krótkie konkretne wskazówki

Możesz także rozważyć następujące kwestie:.

  • W logach unikaj czytelnego formatowania i używaj jednowierszowego JSON, aby zmniejszyć rozmiar.
  • Obiekty, które są często serializowane, powinny być wcześniej uproszczone, np. przez usunięcie niepotrzebnych właściwości.
  • Użyj replacer, aby ograniczyć przeszukiwanie i wykluczyć zbędne właściwości.

Praktyczny przykład: przepływ wysyłania/odbioru zapytania API

Na koniec przedstawiamy sekwencję pokazującą, jak bezpiecznie przekonwertować obiekt do JSON do wysłania na serwer i przywrócić datę z otrzymanej odpowiedzi.

 1// Prepare payload, stringify safely, send via fetch, and revive dates on response.
 2async function sendUserUpdate(url, user) {
 3  // Remove sensitive info before sending
 4  const payload = JSON.stringify(user, (k, v) => (k === "password" ? undefined : v));
 5  const res = await fetch(url, {
 6    method: "POST",
 7    headers: { "Content-Type": "application/json" },
 8    body: payload
 9  });
10
11  const text = await res.text();
12  // Reviver: convert ISO date strings back to Date
13  const data = JSON.parse(text, (key, value) => {
14    if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(value)) {
15      return new Date(value);
16    }
17    return value;
18  });
19  return data;
20}
  • W tym przykładzie password jest wykluczane przed wysłaniem, a data przywracana przez reviver po odebraniu. W rzeczywistej aplikacji należy dodać obsługę błędów i przekroczeń czasu.

Podsumowanie

Dzięki JSON.stringify() i JSON.parse() możesz konwertować obiekty na łańcuchy i odwrotnie. Możesz dostosować proces konwersji, wykorzystując replacer i reviver, a dzięki implementacji toJSON w klasie możesz też kontrolować każdy obiekt osobno. Odniesienia cykliczne, BigInt, funkcje itp. nie mogą być obsłużone bezpośrednio – należy je najpierw odpowiednio przetworzyć.

Zawsze weryfikuj dane zewnętrzne i upewnij się, że poufne informacje nie trafiają do logów ani komunikacji. Przy dużym rozmiarze danych efektywne jest unikanie czytelnego formatowania, rozważanie transmisji różnicowej lub używanie formatów binarnych.

Możesz śledzić ten artykuł, korzystając z Visual Studio Code na naszym kanale YouTube. Proszę również sprawdzić nasz kanał YouTube.

YouTube Video