Jak escapować ciągi JSON: znaki, stringify i pułapki
Escapowanie ciągu JSON oznacza zamianę dowolnego tekstu na ciąg, który może bezpiecznie znaleźć się wewnątrz dokumentu JSON jako literał string. Garstka znaków — podwójny cudzysłów, backslash oraz znaki sterujące, takie jak nowa linia i tabulacja — niesie znaczenie strukturalne albo jest po prostu niedozwolona wewnątrz ciągu JSON, więc każdy z nich zostaje zastąpiony bezpieczną sekwencją escape, taką jak \", \\ czy \n. Zrób to źle, a twój payload przestanie się parsować.
Trafiasz na to bez przerwy: zagnieżdżając jeden obiekt JSON wewnątrz drugiego jako pole tekstowe, wklejając wielowierszowy fragment kodu do wartości konfiguracji albo budując ręcznie treść żądania REST dla curl. Ten przewodnik pokazuje dokładnie, które znaki wymagają escapowania, rozwiewa zamieszanie między escapowaniem a JSON.stringify, prowadzi przez zagnieżdżanie JSON w JSON i escapy Unicode oraz wymienia pułapki, które po cichu psują payloady. Jeśli po prostu chcesz coś teraz zescapować, narzędzie Escape JSON robi to w przeglądarce. Dalsza część wyjaśnia, dlaczego działa tak, jak działa.
Czym jest escapowanie ciągów JSON?
Escapowanie ciągów JSON to proces przekształcania surowego ciągu w postać, którą można bezpiecznie osadzić wewnątrz dokumentu JSON. JSON rezerwuje niewielki zestaw znaków o znaczeniu strukturalnym: podwójny cudzysłów " ogranicza ciąg, a backslash \ rozpoczyna sekwencję escape. Do tego znaki sterujące poniżej U+0020 — nowe linie, tabulacje, powroty karetki — w ogóle nie mogą pojawić się dosłownie wewnątrz ciągu JSON. Escapowanie zastępuje każdy z nich bezpieczną sekwencją, dzięki czemu wynikowy ciąg parsuje się czysto wszędzie.
Kiedy faktycznie tego potrzeba? Kilka sytuacji powraca raz po raz:
- JSON w JSON: koperta webhooka, wiadomość Kafka albo log audytowy przechowuje treść żądania jako pole tekstowe, więc wewnętrzny JSON trzeba zescapować, zanim da się go przypisać.
- Ręcznie pisana konfiguracja: wstawienie wielowierszowego skryptu powłoki, zapytania SQL czy fragmentu kodu do pojedynczej wartości JSON oznacza zamianę każdej nowej linii na
\n. - Treści żądań REST: budowanie treści JSON ręcznie dla
curlalbo klienta HTTP, gdzie cudzysłowy i nowe linie muszą przetrwać powłokę i sieć. - Kodowanie bezpieczne dla logów: zapisywanie treści podanej przez użytkownika do ustrukturyzowanej linii logu bez ryzyka, że wstrzyknięty cudzysłów lub nowa linia uszkodzi format.
Krótkie słowo o kolejności operacji. Jeśli zaczynasz od nieuporządkowanego lub niezaufanego JSON-a, najpierw go zweryfikuj, abyś escapował coś poprawnie zbudowanego — wklej go do narzędzia Formatowanie JSON, żeby ładnie sformatować i sprawdzić, a potem zescapuj już czysty wynik. Escapowanie śmieci daje tylko zescapowane śmieci.
Które znaki muszą być escapowane w JSON
Specyfikacja JSON definiuje precyzyjną, krótką listę. Siedem znaków ma dedykowany dwuznakowy escape, a wszystko inne poniżej U+0020 wraca do escape’u Unicode \uXXXX. Oto pełny zestaw znaków escape w JSON:
| Znak | Escapuje się do | Uwagi |
|---|---|---|
" (U+0022) | \" | Ogranicznik ciągu |
\ (U+005C) | \\ | Początek escape’u (przypadek json escape backslash) |
| nowa linia (U+000A) | \n | |
| powrót karetki (U+000D) | \r | |
| tabulacja (U+0009) | \t | |
| backspace (U+0008) | \b | |
| form feed (U+000C) | \f | |
| inne znaki sterujące < U+0020 | \uXXXX | np. U+0000 → \u0000 |
To, co nie wymaga escapowania, jest równie ważne. Ukośnik / to całkowicie zwykły znak (jego escapowanie jest opcjonalne i przydatne tylko w jednym wąskim przypadku omówionym poniżej). Pojedyncze cudzysłowy nigdy nie wymagają escapowania, ponieważ JSON nie używa ich jako ograniczników. A każdy znak drukowalny od U+0020 wzwyż — w tym wszystkie wielobajtowe znaki UTF-8, jak é, 日 czy 😀 — jest poprawny w niezmienionej postaci.
Oto różnica pokazana konkretnie. Surowe wejście po lewej, escapowany literał ciągu JSON po prawej:
Input:
She said "hello" then left.
Escaped:
"She said \"hello\"\tthen left."
Podwójne cudzysłowy stały się \", a tabulacja stała się \t. Teraz ciąg można bezpiecznie wstawić do dowolnego parsera JSON, linii logu czy treści żądania.
Escape JSON a JSON stringify: na czym polega różnica?
Tu właśnie wielu ludzi się gubi. Escapowanie i JSON.stringify to nie dwie różne operacje, lecz dwa spojrzenia na tę samą.
JSON.stringify(value) serializuje dowolną wartość JavaScript do jej tekstowej reprezentacji JSON. Gdy ta wartość akurat jest ciągiem, jej serializacja oznacza otoczenie go podwójnymi cudzysłowami i zescapowanie znaków specjalnych wewnątrz. To dokładnie escapowanie JSON. Tak więc JSON.stringify("a\tb") zwraca siedmioznakowy ciąg "a\tb", łącznie z cudzysłowami.
Praktyczne pytanie brzmi: czy chcesz te zewnętrzne cudzysłowy. To odpowiada bezpośrednio opcji Otocz podwójnymi cudzysłowami w narzędziu Escape JSON:
| Tryb | Wynik dla wejścia a"b | Kiedy go używać |
|---|---|---|
| Otaczanie włączone | "a\"b" | Kompletny literał ciągu JSON, identyczny z JSON.stringify. Przypisz go do zmiennej albo wklej po dwukropku. |
| Otaczanie wyłączone | a\"b | Tylko zescapowane ciało, bez otaczających cudzysłowów. Użyj, gdy sam wpisujesz cudzysłowy w dokumencie JSON. |
Jeśli więc szukałeś „json stringify” i tu trafiłeś, model mentalny jest prosty: stringify ciągu = escape z włączonym otaczaniem. Postać bez cudzysłowów to to samo z odjętymi zewnętrznymi cudzysłowami.
Jak zescapować ciąg dla JSON w kodzie
Złota zasada: nigdy nie buduj ręcznie łańcucha wywołań replace(). Każdy popularny język dostarcza serializer JSON, który poprawnie obsługuje cudzysłowy, backslashe, znaki sterujące i Unicode. Po niego sięgaj.
JavaScript
const text = 'She said "hi"\nthen left.';
const escaped = JSON.stringify(text);
console.log(escaped);
// "She said \"hi\"\nthen left."
JSON.stringify na ciągu daje kompletny literał z cudzysłowami. Chcesz tylko ciało? Odetnij pierwszy i ostatni znak: JSON.stringify(text).slice(1, -1).
Python
import json
text = 'She said "hi"\nthen left.'
print(json.dumps(text))
# "She said \"hi\"\nthen left."
print(json.dumps(text, ensure_ascii=False))
# "She said \"hi\"\nthen left." (non-ASCII kept as UTF-8)
json.dumps domyślnie używa ensure_ascii=True, co escapuje każdy znak spoza ASCII do \uXXXX, czyli to samo zachowanie, co tryb ASCII-safe w narzędziu. Przekaż ensure_ascii=False, aby zachować surowy UTF-8.
PHP
<?php
$text = "café \"quoted\"\nline";
echo json_encode($text);
// "caf\u00e9 \"quoted\"\nline" (default escapes non-ASCII to \uXXXX)
echo json_encode($text, JSON_UNESCAPED_UNICODE);
// "café \"quoted\"\nline"
json_encode domyślnie escapuje zarówno znaki spoza ASCII, jak i ukośniki. Dodaj JSON_UNESCAPED_UNICODE, aby zachować czytelność akcentów, oraz JSON_UNESCAPED_SLASHES, aby zostawić / w spokoju.
Go i Java
W Go json.Marshal(text) zwraca zescapowane bajty w cudzysłowach:
b, _ := json.Marshal(`a "quoted" line`)
// b == `"a \"quoted\" line"`
W Javie objectMapper.writeValueAsString(text) z Jacksona albo JSONObject.quote(text) z org.json dają ten sam literał w cudzysłowach. Niezależnie od języka, oprzyj się na bibliotece, bo zna ona już każdy przypadek brzegowy, o którym byś zapomniał.
Osadzanie JSON wewnątrz JSON (JSON w JSON)
To najczęstszy powód, dla którego ludzie escapują JSON ręcznie. Koperta webhooka, rekord kolejki komunikatów albo log audytowy często przechowuje całą treść żądania jako pole string. Aby to zrobić, wewnętrzny JSON trzeba najpierw zescapować.
Zobacz, jak mały obiekt przechodzi przez dwie warstwy kodowania:
1. Inner object: {"a":1}
2. Escaped as a string: "{\"a\":1}"
3. Placed in envelope: {"payload": "{\"a\":1}"}
Każdy " w wewnętrznym obiekcie stał się \", a całość została otoczona jedną zewnętrzną parą cudzysłowów. Wynikiem jest pojedyncza poprawna wartość typu string, którą możesz przypisać do payload.
Haczyk przy głębszym zagnieżdżaniu polega na tym, że backslashe się mnożą. Escapowanie już zescapowanego ciągu escapuje także jego backslashe, więc każda warstwa z grubsza je podwaja: wewnętrzny cudzysłów, który był \", staje się \\\" o jeden poziom dalej i \\\\\" o kolejny poziom dalej. JSON w JSON na trzy poziomy jest naprawdę trudny do odczytania, dlatego pomaga narzędzie. Aby pójść w drugą stronę i wyciągnąć wewnętrzny obiekt z powrotem z ciągu, przepuść go przez narzędzie Unescape JSON.
Unicode i escapowanie \uXXXX
Domyślnie JSON dobrze radzi sobie z surowym UTF-8. é pozostaje é, 日 pozostaje 日, a dokument jest przez to czytelniejszy. Nie musisz escapować żadnego drukowalnego znaku Unicode.
Kiedy więc warto sięgnąć po wyjście ASCII-safe \uXXXX? Tylko wtedy, gdy systemowi po drugiej stronie nie można zaufać z UTF-8: stare bramki SOAP lub XML, niektóre potoki logowania, nagłówki e-mail albo pliki źródłowe, które muszą pozostać czystym ASCII. W trybie ASCII-safe każdy znak powyżej U+007F staje się escape’em \uXXXX, więc café zmienia się w caf\u00e9. Jest to bardziej zaszumione, ale bajt w bajt ASCII i dekoduje się z powrotem do oryginału w każdym zgodnym parserze.
Jest tu jeden niuans. \uXXXX koduje pojedynczą 16-bitową jednostkę kodową UTF-16, ale znaki spoza Basic Multilingual Plane — emoji, rzadkie pisma — potrzebują 21 bitów. JSON obsługuje je za pomocą pary surogatów: dwóch escape’ów \uXXXX jeden po drugim. Uśmiechnięta buźka 😀 (U+1F600) staje się \ud83d\ude00. Większość serializerów robi to za ciebie; ryzyko pojawia się przy ręcznie pisanym escaperze, który emituje pojedynczy, niesparowany surogat.
Jeśli pary surogatów i punkty kodowe to dla ciebie nowy teren, Przewodnik UTF-8 vs UTF-16 vs Unicode rozkłada dokładnie na czynniki, jak pojedynczy znak mapuje się na bajty i jednostki kodowe. To brakujący kontekst stojący za tym, dlaczego jedno emoji potrzebuje dwóch escape’ów.
Unescapowanie: odczytywanie escapowanego JSON z powrotem
Escapowanie ma operację odwrotną. Aby zamienić "a\tb" z powrotem na prawdziwy tekst z tabulacją, parsujesz go: JSON.parse(str) w JavaScript, json.loads(str) w Pythonie. Parser przechodzi przez każdą sekwencję escape i odtwarza oryginalne znaki, wraz z parami surogatów.
Gdy unescapowanie się nie powiedzie, błędem jest niemal zawsze „invalid escape sequence” i ma on kilka typowych przyczyn:
- Samotny backslash przed znakiem, którego JSON nie rozpoznaje jako escape, jak
\q. - Zmyślony escape, taki jak
\x41— JSON nie ma szesnastkowego escape’u\x; używa tylko\u. - Ucięty escape
\uz mniej niż czterema cyframi szesnastkowymi, jak\u00. - Zabłąkany lub niezbalansowany podwójny cudzysłów, który przerywa granicę ciągu.
Sprawdź, czy każdy backslash rozpoczyna jeden z poprawnych escape’ów (\n \r \t \b \f \" \\ \/ \uXXXX) i czy cudzysłowy są w parach. W przypadku escapowanych ciągów skopiowanych ze środka linii logu — gdzie zewnętrzne cudzysłowy zostały pominięte — narzędzie Unescape JSON przyjmuje ciało z otaczającymi cudzysłowami lub bez nich i dekoduje je tak czy inaczej.
Typowe pułapki escapowania JSON
Większość zepsutych payloadów wynika z jednego z tych sześciu błędów.
1. Podwójne escapowanie. Escapowanie tekstu, który już był escapowany, zamienia \n na \\n, a \" na \\\", więc konsument odczytuje dosłowny backslash-n zamiast nowej linii. Zwykle dzieje się tak, gdy usługa wyżej w łańcuchu już zescapowała wartość w JSON, a ty escapujesz ją ponownie. Najpierw unescapuj, aby sprawdzić bieżący stan, a potem zescapuj dokładnie raz.
2. Zapominanie o zewnętrznych cudzysłowach. Przy wyłączonym otaczaniu dostajesz tylko zescapowane ciało, nie kompletny ciąg. Wklejenie hello \"world\" wprost tam, gdzie oczekiwana jest wartość JSON, jest nieprawidłowe, bo brakuje otaczających cudzysłowów. Albo zostaw otaczanie włączone, albo wpisz cudzysłowy samodzielnie.
3. Nadmierne escapowanie znaków spoza ASCII. Włączenie trybu ASCII-safe, gdy konsument dobrze radzi sobie z UTF-8, tylko rozdyma wyjście. café staje się caf\u00e9 bez powodu — trudniejsze do odczytania, większe w sieci, zerowa korzyść. Zostaw to wyłączone, chyba że konkretny starszy system wymaga czystego ASCII.
4. Escapowanie ukośnika z odruchu. Escape / ma znaczenie w dokładnie jednym miejscu: JSON wstawiony wewnątrz znacznika HTML <script>, gdzie podciąg </script> zamknąłby znacznik przedwcześnie, niezależnie od kontekstu JSON. Escapowanie / do \/ to neutralizuje. Poza tym jednym przypadkiem escapowanie ukośników to czysty bałagan. Zostaw je wyłączone dla treści REST, plików konfiguracyjnych i payloadów komunikatów.
5. Ręcznie pisane łańcuchy replace. Ręczny potok replace('"', '\\"') niemal zawsze o czymś zapomina: o znaku sterującym, backspasie, parze surogatów. Użyj serializera języka, który pokrywa całą specyfikację.
6. Escapowanie, ale nigdy unescapowanie (lub unescapowanie dwa razy). Pełny obieg musi się równoważyć. Zescapuj raz na wejściu, unescapuj raz na wyjściu. Unescapuj dwa razy, a zniekształcisz prawdziwe backslashe, które były częścią danych.
Jeszcze jedno rozróżnienie warte przygwożdżenia: escapowanie JSON to nie kodowanie URL ani procentowe. Rozwiązują różne problemy dla różnych transportów, a ich mieszanie — kodowanie procentowe wartości, a następnie escapowanie wyniku w JSON, albo na odwrót — daje bałagan, którego żaden parser nie odczyta czysto. Przewodnik kodowanie i dekodowanie URL omawia, kiedy kodowanie procentowe jest właściwym narzędziem i czym różni się od tego, co robi JSON.
Najczęściej zadawane pytania
Co to znaczy zescapować ciąg w JSON?
Oznacza to zastąpienie znaków, które niosą dla JSON-a znaczenie strukturalne — podwójnego cudzysłowu, backslasha oraz znaków sterujących, takich jak nowa linia i tabulacja — bezpiecznymi sekwencjami escape, takimi jak \", \\ i \n. Wynik można osadzić jako literał string wewnątrz dokumentu JSON bez psucia parsowania.
Które znaki trzeba escapować w JSON?
Podwójny cudzysłów, backslash, nowa linia, powrót karetki, tabulacja, backspace i form feed mają każdy dedykowany escape, a każdy inny znak sterujący poniżej U+0020 staje się \uXXXX. Znaki drukowalne i wielobajtowy UTF-8 nie wymagają escapowania; ukośnik jest opcjonalny i ma znaczenie tylko wewnątrz znaczników HTML <script>.
Czy escape JSON to to samo co JSON.stringify?
Przeważnie dwa spojrzenia na jedną operację. JSON.stringify zastosowany do ciągu otacza go podwójnymi cudzysłowami i escapuje wewnątrz znaki specjalne, czyli właśnie wykonuje escapowanie JSON. Otaczanie włączone równa się postaci w cudzysłowach (identycznej z JSON.stringify); otaczanie wyłączone daje tylko zescapowane ciało bez otaczających cudzysłowów.
Jak zescapować ciąg dla JSON w JavaScript lub Pythonie?
W JavaScript użyj JSON.stringify(str); w Pythonie użyj json.dumps(str). Zawsze polegaj na wbudowanej funkcji, a nie na ręcznie pisanym łańcuchu replace. Wbudowane funkcje poprawnie obsługują Unicode, znaki sterujące i każdy przypadek brzegowy, który inaczej byś przeoczył.
Dlaczego mój JSON psuje się przez dodatkowe backslashe?
Zwykłą przyczyną jest podwójne escapowanie: escapowanie tekstu, który już był escapowany, przez co \n staje się \\n, a konsument odczytuje dosłowny backslash-n zamiast nowej linii. Najpierw unescapuj wartość, aby sprawdzić jej rzeczywisty stan, a potem zescapuj ją dokładnie raz.
Czy muszę escapować ukośniki lub Unicode w JSON?
Żadne nie jest wymagane. / to zwykły znak i wymaga escapowania tylko wtedy, gdy wstawiasz JSON do znacznika HTML <script>, aby powstrzymać sekwencję </script> przed przedwczesnym zamknięciem. Unicode domyślnie pozostaje surowym UTF-8; używaj \uXXXX tylko wtedy, gdy system po drugiej stronie nie radzi sobie z UTF-8.