Para qué sirve el escapado
Una cadena JSON se delimita con comillas dobles. Así que en cuanto el texto interior necesita una comilla doble propia, hay un conflicto: el parser leería esa comilla como el fin de la cadena. El escapado lo resuelve poniendo una barra invertida delante — \" significa un carácter de comilla literal, no un delimitador.
El mismo truco cubre todo lo demás que no puede aparecer en crudo dentro de una cadena: la propia barra invertida y los caracteres de control por debajo de U+0020.
| Escape | Significa |
|---|---|
\" | Una comilla doble, no el fin de la cadena |
\\ | Una barra invertida literal |
\n | Salto de línea |
\r | Retorno de carro |
\t | Tabulación |
\b \f | Retroceso y salto de página |
\/ | Una barra normal. Legal, pero nunca obligatoria |
\uXXXX | Cualquier carácter por punto de código |
Qué no necesita escaparse
Vale la pena decirlo porque escapar de más es tan común como escapar de menos. El texto no ASCII no necesita escaparse en absoluto. JSON es Unicode, así que las letras acentuadas, los caracteres chinos y los emoji son válidos en crudo dentro de una cadena.
- Las barras normales nunca lo necesitan.
https://example.comestá bien tal cual.\/es legal y algunos codificadores lo emiten, y por eso las URLs en los logs a menudo se ven raras sin motivo. - Las comillas simples nunca lo necesitan. No tienen significado especial en JSON, así que
\'no es un escape válido: es un error. - Los caracteres de control siempre lo necesitan. Una tabulación o un salto de línea en crudo dentro de una cadena es JSON inválido. Se vuelven
\ty\n; algo más raro se vuelve del estilo\u0001.
JSON de entrada
{"s":"café 中文 😀"}Tras escapar
"{\"s\":\"café 中文 😀\"}"café, 中文 y el emoji pasaron intactos — \u00e9 también habría sido legal, solo ilegible.Caracteres de control
Una tabulación y un salto de línea dentro de un valor de cadena no pueden escribirse literalmente, así que salen como escapes de dos caracteres. Es el único caso donde el escapado cambia el tamaño de forma que la gente lo nota: una cadena multilínea se alarga visiblemente.
JSON de entrada
{"s":"a\tb\r\nc"}Tras escapar
"{\"s\":\"a\\tb\\r\\nc\"}"\t de la entrada ya era un escape. Tras una ronda es \\t — una barra invertida seguida de una t, que es cómo una capa anidada lo preserva.Por qué se multiplican las barras
Aquí está la causa real del desastre. Un servicio registra un cuerpo de petición metiendo el documento JSON entero en un campo de cadena de otro documento JSON. Las comillas del documento interior quedan ahora dentro de una cadena, así que cada una recibe una barra invertida.
Los datos
{"user":{"name":"Ada"},"ok":true}Guardados como cadena JSON
"{\"user\":{\"name\":\"Ada\"},\"ok\":true}"Dos capas, y la duplicación
Hazlo dos veces — un servicio registra un mensaje que ya contenía datos registrados — y cada barra invertida de la primera ronda necesita ahora escaparse a su vez. Una comilla se vuelve \\\": una barra invertida escapada seguida de una comilla escapada.
Por eso el número parece arbitrario. No lo es: cada capa aproximadamente duplica las barras, así que 1, 3 o 7 barras significan una, dos o tres capas de codificación.
Una capa
"{\"user\":{\"name\":\"Ada\"},\"ok\":true}"Dos capas
"\"{\\\"user\\\":{\\\"name\\\":\\\"Ada\\\"},\\\"ok\\\":true}\""Recuperar los datos
Desescapar es la inversa, una capa a la vez. Introduce la cadena escapada y recuperas el documento, ya formateado:
- Una pulsación quita una capa. Una cadena doblemente codificada vuelve como una cadena aún escapada, no como el documento. Púlsalo otra vez.
- El botón está deshabilitado cuando no hay nada que quitar. Solo se activa cuando la entrada se parsea como una cadena que a su vez se parsea como JSON, así que un clic accidental no puede estropear un documento normal.
- Si pegas datos escapados, el formateador lo detecta. Lo dice en lugar de reformatear en silencio, porque una cadena que casualmente contiene JSON y un documento JSON son cosas distintas.
Entrada escapada
"{\"user\":{\"name\":\"Ada\"},\"ok\":true}"Tras quitar los escapes
{
"user": {
"name": "Ada"
},
"ok": true
}Evitarlo desde el principio
La codificación anidada casi siempre es accidental. Si controlas el código que la produce, estas son las soluciones, ordenadas por cuánto ayudan.
- Registra estructurado, no serializado. La mayoría de los loggers aceptan un objeto para un campo.
logger.info({ body: payload })anida correctamente;JSON.stringify(payload)crea la capa que luego tienes que pelar. - No serialices antes de guardar en una columna JSON. El
jsonbde Postgres y equivalentes toman el valor directamente. Serializar primero guarda una cadena, y cada consulta contra ella necesita un cast. - Nunca construyas JSON concatenando cadenas. De ahí viene el escapado insuficiente: una comilla en el apellido de alguien termina la cadena antes de tiempo y todo el payload se vuelve inválido. Serializa con una librería.
- Revisa los payloads de las colas de mensajes. Un cuerpo que ya es JSON no necesita recodificarse al entrar en una cola que transporta texto.
Pruébalo, o sigue leyendo
- Formateador JSONTiene Escapar y Quitar escapes en la barra, y detecta entrada escapada al pegar.
- Validación y Schema JSONLa otra mitad de por qué se rechaza un payload.
- ¿Puede JSON llevar comentarios?Otra cosa que la especificación no permite, y qué hacer al respecto.
- Guía completa de JSONSintaxis, tipos y las reglas detrás de los errores.