LW IT Solutions logo LW IT Solutions logo LW IT Solutions
« Przegląd bloga /Tworzenie stron internetowych/Tutorials / Tutorial: Sprawdzenie JSON-a, zanim trafi do tagu
Ten artykuł w innych językach:

Tutorial: Sprawdzenie JSON-a, zanim trafi do tagu

Tutorial: Sprawdzenie JSON-a, zanim trafi do tagu

Przejdź do interaktywnego przykładu ↓

Spis treści
  1. Dlaczego obiekt JavaScript nie jest JSON-em
  2. Pięć błędów zgłaszających wyjątek
  3. Dwa, które nic nie zgłaszają
  4. Odczyt zgłaszanej pozycji
  5. Sprawdzenie w wierszu poleceń
  6. Gdzie liczy się to najbardziej
  7. Pytania i odpowiedzi
  8. Źródła

Dokument JSON może wyglądać poprawnie, a mimo to zostać odrzucony przez aplikację. Może też zostać przyjęty, choć liczba zostanie odczytana inaczej, niż zakładano. Łatwiej znaleźć przyczynę, gdy oddzielnie sprawdzisz składnię i znaczenie danych.

Przejdziemy przez małe przykłady typowych problemów: cudzysłowy, nadmiarowe przecinki, powtórzone klucze i duże identyfikatory. Chodzi o to, żeby następny komunikat błędu pomagał, zamiast zmuszać do przypadkowego przeszukiwania całego dokumentu.

Zawartość z siedmioma ponumerowanymi zastrzeżeniami zaznaczonymi w tekście, pod spodem dwie kolumny oddzielające pięć zgłaszających wyjątek od dwóch cichych
Pięć z siedmiu zatrzymuje parser. Pozostałe dwa dają dokument czytający się czysto i znaczący co innego.

Dlaczego obiekt JavaScript nie jest JSON-em

Wszystko poprawne w JSON-ie jest poprawnym JavaScriptem. Odwrotnie już nie, a w tym odstępie mieszkają błędy.

Tekst
{                                     // JavaScript, nie JSON
  'order_id': 9007199254740993,       // pojedyncze cudzyslowy
  total: 129.90,                      // klucz bez cudzyslowow
  items: [ { sku: "SKU-42", qty: 2, } ],   // przecinek przed nawiasem
  rabatt: NaN,                        // nie jest wartoscia JSON
  /* komentarz */
}

JSON dopuszcza cudzysłowy podwójne i żadnych innych, wymaga cudzysłowów przy każdym kluczu, zabrania przecinka przed nawiasem zamykającym, nie zna komentarzy i zna dokładnie siedem rodzajów wartości: obiekt, listę, ciąg znaków, liczbę, true, false oraz null. Nie ma undefined, nie ma NaN, nie ma Infinity i nie ma daty.

Daty często budzą zaskoczenie, bo JSON nie ma dla nich osobnej reprezentacji. Nadawca i odbiorca muszą uzgodnić, jak odczytywać ciąg znaków lub liczbę jako datę. Parser nie wykryje, że każda ze stron rozumie ten format inaczej.

Pięć błędów zgłaszających wyjątek

To te głośne, a każdy z nich daje ten sam mało pomocny komunikat o nieoczekiwanym tokenie.

BłądJak wygląda
Pojedyncze cudzysłowy’order_id’ zamiast „order_id”
Klucz bez cudzysłowówtotal: 129.90 zamiast „total”: 129.90
Przecinek przed nawiasemPrzecinek przed } albo ]
KomentarzWszystko po // albo między /* a */
Surowy koniec linii w ciąguZnak nowej linii w cudzysłowach; potrzebne jest \n

Szósty należy do tej samej grupy i wcale nie jest widoczny: znacznik kolejności bajtów na początku pliku. To trzy niewidoczne bajty przed nawiasem otwierającym, a parser zgłasza nieoczekiwany token na pozycji 0 – co wygląda, jakby problemem był nawias. Plik wyglądający bezbłędnie i zawodzący na pozycji 0 taki znacznik ma, a edytor z ustawieniem zapisu bez znacznika go usuwa.

Dwa, które nic nie zgłaszają

Tych warto szukać celowo, bo nic innego na nie nie wskaże.

Pierwszym jest zdublowany klucz. Specyfikacja go dopuszcza, a zachowaniem zwyczajowym jest, że wygrywa ostatni – dokument z dwoma "total" czyta się więc czysto i niesie wartość drugą. Gdy oba leżą w długiej zawartości daleko od siebie, powstaje liczba ani niewyglądająca błędnie, ani niebędąca poprawną.

Tekst
{ "total": 129.90, "currency": "EUR", "total": 139.90 }

JSON.parse(...).total   →   139.90

Drugim jest duża liczba całkowita. Liczby w JavaScripcie są zmiennoprzecinkowe, a całkowite pozostają dokładne tylko do 2^53 − 1. Numer zamówienia, identyfikator z bazy danych, identyfikator Meta CAPI – wszystkie regularnie to przekraczają, a parser zaokrągla je bez słowa.

Tekst
JSON.parse('{"id": 9007199254740993}').id   →   9007199254740992

JSON.parse('{"id": "9007199254740993"}').id →   "9007199254740993"

Wynikająca zasada jest krótka: identyfikator jest ciągiem znaków, zawsze, także gdy składa się z samych cyfr. Nikt na nim i tak nie liczy, a ciąg znaków przechodzi przez każdy parser bez zmian.

Odczyt zgłaszanej pozycji

Pozycja w komunikacie błędu to przesunięcie znakowe w całym dokumencie, a w zawartości zminifikowanej jest to liczba bez znaczenia.

Pierwszym krokiem jest więc zawsze sformatowanie dokumentu, a nie szukanie błędu. Sformatowana zawartość układa pozycję na linii, a linia powyżej wskazanej jest zwykle właściwym miejscem usterki – brakujący przecinek rzuca się w oczy przy następnym tokenie, a nie tam, gdzie go brakuje.

JavaScript
try {
  JSON.parse(text);
} catch (e) {
  const stelle = Number((e.message.match(/position (\d+)/) || [])[1]);
  if (!Number.isNaN(stelle)) {
    console.log(text.slice(Math.max(0, stelle - 60), stelle + 60));
    console.log(" ".repeat(Math.min(60, stelle)) + "^");
  }
}

Sześćdziesiąt znaków po każdej stronie wystarcza niemal zawsze, aby zobaczyć problem, a daszek zdejmuje konieczność liczenia. Warto zachować to jako gotowy fragment, bo alternatywą jest otwarcie formatera, wklejenie, przewijanie i utrata pozycji.

Sprawdzenie w wierszu poleceń

Gdy zawartość pochodzi z pliku albo z żądania, na pytanie odpowiada jedno polecenie.

Shell
# poprawny? sformatowane wyjscie albo blad z numerem linii
jq . nutzlast.json

# sama odpowiedz, bez tresci
jq -e . nutzlast.json >/dev/null && echo "poprawny" || echo "niepoprawny"

# znalezc zdublowane klucze - --stream zglasza kazda pare, takze powtorzona
jq -c --stream 'select(length == 2) | .[0]' nutzlast.json | sort | uniq -d

# duze liczby, ktore straca dokladnosc
grep -oE '"[a-z_]+": *[0-9]{16,}' nutzlast.json

Trzecie polecenie opłaca się na każdej nigdy niesprawdzanej zawartości. Wypisuje ścieżki występujące więcej niż raz, a to maszynowa droga do przypadku cichego – przy zawartości generowanej znajduje coś zaskakująco często, bo szablon dopisujący pole w dwóch gałęziach wytwarza dokładnie to.

Gdzie liczy się to najbardziej

Trzy miejsca zamieniają drobny błąd JSON w coś trudnego do zauważenia później.

Blok JSON-LD na stronie jest pierwszym. Błąd składni oznacza, że wyszukiwarki nie odczytują żadnych danych strukturalnych, a strona nadal działa bez zarzutu – nic w witrynie nie wskazuje więc na problem, a strata pojawia się tygodnie później jako brak wyników rozszerzonych. Kontrolą, która to wyłapuje, jest walidator na stronie wysłanej, a nie na szablonie.

Zawartość Conversions API jest drugim. Platforma odbierająca odpowiada błędem, ale ten błąd stoi w odpowiedzi serwer-serwer, której nikt nie obserwuje, a nieudane zdarzenie po prostu się nie pojawia. To tutaj zdublowany klucz czyni największą szkodę: zawartość z wartością zdarzenia dwa razy wysyła tę błędną, a i nadawca, i odbiorca uważają żądanie za udane.

Konfiguracja szablonu menedżera tagów jest trzecim. Jej JSON czytany jest raz przy wczytaniu szablonu, a błędne pole potrafi zostawić szablon zainstalowany, lecz bezczynny. Ten przypadek różni się tym, że zawodzi przy imporcie, a nie w trakcie działania – to lepsze miejsce na porażkę, i tylko wtedy, gdy ktoś przeczyta komunikat.

Wszystkim trzem wspólne jest jedno: sprawdza się tam, gdzie zawartość powstaje, a nie tam, gdzie zostaje zużyta. Krok budowania przepuszczający parser przez każdy wytworzony plik nic nie kosztuje i wyłapuje wszystkie pięć głośnych błędów, zanim opuszczą maszynę.

Pytania i odpowiedzi

Czy przy zdublowanym kluczu w każdym parserze wygrywa ostatnia wartość?

Nie, i właśnie w tym tkwi większe niebezpieczeństwo. Specyfikacja JSON, RFC 8259, mówi jedynie, że nazwy w obiekcie powinny być unikalne, i wyraźnie odnotowuje, że odbiorcy zachowują się przy zdublowanych nazwach różnie: wiele implementacji zachowuje ostatnią wartość, inne zgłaszają błąd, a jeszcze inne przekazują dalej wszystkie pary. JSON.parse, moduł json w Pythonie i jq zachowują ostatnią.

Kłopot pojawia się, gdy tylko dwa miejsca czytają tę samą zawartość różnymi parserami. Jeśli walidator sprawdza pierwszą wartość, a odbiorca przetwarza ostatnią, sprawdzenie widziało inną wartość niż odbiorca, a obie strony zgłaszają sukces.

We własnym kodzie da się ten przypadek twardo odrzucić. W Pythonie wystarczy object_pairs_hook, który liczy klucze każdego obiektu i przy zdublowaniu zgłasza wyjątek; cichy przypadek staje się wtedy równie głośny jak pięć błędów z tabeli.

Czy jq . pokazuje dużą liczbę dokładnie tak, jak stoi w pliku?

To zależy od wersji. Do jq 1.6 każda liczba jest wczytywana jako zmiennoprzecinkowa, a sformatowane wyjście pokazuje 9007199254740993 jako 9007199254740992, tak samo jak JSON.parse, więc zapisana kopia tego wyjścia traci cyfrę; dopiero od jq 1.7 liczba z pliku pozostaje niezmieniona, dopóki nie wykonuje się na niej obliczeń. Niezawodne jest więc polecenie grep z artykułu, bo czyta tekst pliku, a nie sparsowaną wartość.

Zrozum przez eksperyment

Jedna minuta: znajdziesz błąd?

Dlaczego ten JSON jest niepoprawny?

{"event":"page_view","value":42,}

Przejdź do odpowiedniego narzędzia →

Źródło ↗

Twoja ścieżka nauki →

Jeden problem, konkretna droga do rozwiązania

Mój JSON sprawia problemy

  1. Zrozum: Sprawdź dane JSON
  2. Wypróbuj: Formatuj JSON
  3. Zacznij od małego przykładu i znajdź błędy składni. Następnie sprawdź, czy wartości i typy pasują do aplikacji – sama poprawna składnia tego nie gwarantuje.
Lukas Wójcik

Lukas Wójcik

Systems architect and technology enthusiast specializing in scalable tracking solutions, GMP Stack (GA4 & GTM), and robust backend architectures. Advocate for clean code and privacy-first design.

Kontakt

Masz pytanie lub pomysł na projekt internetowy? Napisz krótko, o co chodzi. Formularz korzysta z reCAPTCHA do ochrony przed spamem.

Napisanie komentarza

Przypadki brzegowe pominięte w artykule i pytania o kod są tu mile widziane.

Adres e-mail nie jest publikowany. Pola obowiązkowe oznaczono gwiazdką.