Cómo escapar cadenas JSON: caracteres, stringify y errores
Escapar una cadena JSON significa convertir un texto cualquiera en una cadena que pueda alojarse sin problemas dentro de un documento JSON como literal de cadena. Un puñado de caracteres (las comillas dobles, la barra invertida y caracteres de control como el salto de línea y la tabulación) tienen un significado estructural o son sencillamente ilegales dentro de una cadena JSON, así que cada uno se reemplaza por una secuencia de escape segura como \", \\ o \n. Si lo haces mal, tu carga útil deja de parsearse.
Te topas con esto a cada rato: anidar un objeto JSON dentro de otro como campo de cadena, pegar un fragmento de código de varias líneas en un valor de configuración o armar a mano el cuerpo de una solicitud REST para curl. Esta guía cubre exactamente qué caracteres hay que escapar, aclara la confusión entre escapar y JSON.stringify, recorre el anidamiento JSON-en-JSON y los escapes Unicode, y enumera los errores que rompen cargas útiles sin que te des cuenta. Si solo quieres escapar algo ahora mismo, nuestra herramienta Escape JSON lo hace en el navegador, pero sigue leyendo para entender por qué funciona como funciona.
¿Qué es el escape de cadenas JSON?
El escape de cadenas JSON es el proceso de convertir una cadena en bruto en una forma segura para incrustarla dentro de un documento JSON. JSON reserva un pequeño conjunto de caracteres con significado estructural: las comillas dobles " delimitan una cadena y la barra invertida \ inicia una secuencia de escape. Los caracteres de control por debajo de U+0020 (saltos de línea, tabulaciones, retornos de carro) tampoco pueden aparecer literalmente dentro de una cadena JSON. El escape reemplaza cada uno por una secuencia segura para que la cadena resultante se parsee sin problemas en cualquier lugar.
¿Cuándo lo necesitas de verdad? Hay algunas situaciones que se repiten una y otra vez:
- JSON-en-JSON: un sobre de webhook, un mensaje de Kafka o un registro de auditoría guarda el cuerpo de una solicitud como campo de cadena, así que el JSON interno debe escaparse antes de poder asignarlo.
- Configuración escrita a mano: meter un script de shell de varias líneas, una consulta SQL o un fragmento de código en un solo valor JSON implica convertir cada salto de línea en
\n. - Cuerpos de solicitudes REST: construir un cuerpo JSON a mano para
curlo un cliente HTTP, donde las comillas y los saltos de línea deben sobrevivir al shell y a la transmisión. - Codificación segura para registros: escribir contenido proporcionado por el usuario en una línea de registro estructurada sin que una comilla o un salto de línea inyectados corrompan el formato.
Una nota rápida sobre el orden de las operaciones. Si partes de un JSON desordenado o no confiable, valídalo primero para que estés escapando algo bien formado: pégalo en el Formateador JSON para imprimirlo con formato y revisarlo, y luego escapa el resultado limpio. Escapar basura solo te da basura escapada.
Qué caracteres se deben escapar en JSON
La especificación de JSON define una lista breve y precisa. Siete caracteres tienen un escape dedicado de dos caracteres, y todo lo demás por debajo de U+0020 recurre a un escape Unicode \uXXXX. Este es el conjunto completo de caracteres de escape de JSON:
| Carácter | Se escapa a | Notas |
|---|---|---|
" (U+0022) | \" | Delimitador de cadena |
\ (U+005C) | \\ | Inicio de escape (el caso de la barra invertida en JSON) |
| salto de línea (U+000A) | \n | |
| retorno de carro (U+000D) | \r | |
| tabulación (U+0009) | \t | |
| retroceso (U+0008) | \b | |
| avance de página (U+000C) | \f | |
| otros controles < U+0020 | \uXXXX | p. ej. U+0000 → \u0000 |
Lo que no necesita escaparse es igual de importante. La barra inclinada / es un carácter perfectamente normal (escaparla es opcional y solo útil en un caso muy concreto que se cubre más abajo). Las comillas simples nunca necesitan escaparse porque JSON no las usa como delimitadores. Y todo carácter imprimible igual o superior a U+0020 es válido tal cual, incluidos todos los caracteres UTF-8 multibyte como é, 日 o 😀.
Aquí está la diferencia en concreto. La entrada en bruto a la izquierda, el literal de cadena JSON escapado a la derecha:
Input:
She said "hello" then left.
Escaped:
"She said \"hello\"\tthen left."
Las comillas dobles pasaron a ser \" y la tabulación pasó a ser \t. Ahora la cadena se puede soltar sin riesgo en cualquier parser JSON, línea de registro o cuerpo de solicitud.
JSON escape vs JSON stringify: ¿cuál es la diferencia?
Este es el punto que la mayoría de los tutoriales se salta, y confunde a mucha gente. Escapar y JSON.stringify no son dos operaciones distintas: son dos vistas de la misma.
JSON.stringify(value) serializa cualquier valor de JavaScript en su representación textual JSON. Cuando ese valor resulta ser una cadena, serializarla significa envolverla en comillas dobles y escapar los caracteres especiales que contiene. Eso es exactamente el escape de JSON. Así, JSON.stringify("a\tb") devuelve la cadena de siete caracteres "a\tb", comillas incluidas.
La pregunta práctica es si quieres esas comillas externas. Eso se corresponde directamente con la opción Envolver en comillas dobles de la herramienta Escape JSON:
| Modo | Salida para la entrada a"b | Cuándo usarlo |
|---|---|---|
| Envolver activado | "a\"b" | Un literal de cadena JSON completo, idéntico a JSON.stringify. Asígnalo a una variable o pégalo después de dos puntos. |
| Envolver desactivado | a\"b | Solo el cuerpo escapado, sin comillas alrededor. Úsalo cuando vayas a escribir tú mismo las comillas en un documento JSON. |
Así que si buscaste “json stringify” y llegaste aquí, el modelo mental es simple: hacer stringify de una cadena = escape con envolver activado. La forma sin comillas es lo mismo, pero con las comillas externas quitadas.
Cómo escapar una cadena para JSON en código
La regla de oro: nunca improvises una cadena de llamadas a replace(). Todos los lenguajes principales incluyen un serializador JSON que maneja correctamente las comillas, las barras invertidas, los caracteres de control y Unicode. Recurre a él.
JavaScript
const text = 'She said "hi"\nthen left.';
const escaped = JSON.stringify(text);
console.log(escaped);
// "She said \"hi\"\nthen left."
JSON.stringify sobre una cadena te da el literal completo y entre comillas. ¿Quieres solo el cuerpo? Recorta el primer y el último carácter: 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 usa por defecto ensure_ascii=True, que escapa cada carácter no ASCII a \uXXXX: el mismo comportamiento que el modo seguro para ASCII de la herramienta. Pasa ensure_ascii=False para conservar el UTF-8 en bruto.
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 escapa por defecto tanto los caracteres no ASCII como las barras inclinadas. Añade JSON_UNESCAPED_UNICODE para mantener legibles los acentos, y JSON_UNESCAPED_SLASHES para dejar la / en paz.
Go y Java
En Go, json.Marshal(text) devuelve los bytes escapados y entre comillas:
b, _ := json.Marshal(`a "quoted" line`)
// b == `"a \"quoted\" line"`
En Java, objectMapper.writeValueAsString(text) de Jackson o JSONObject.quote(text) de org.json producen el mismo literal entre comillas. Sea cual sea el lenguaje, apóyate en la biblioteca: ya conoce cada caso límite que tú olvidarías.
Incrustar JSON dentro de JSON (JSON-en-JSON)
Esta es, con diferencia, la razón más común por la que la gente escapa JSON a mano. Un sobre de webhook, un registro de una cola de mensajes o un registro de auditoría a menudo guarda un cuerpo de solicitud entero como campo de cadena. Para lograrlo, el JSON interno debe escaparse primero.
Observa cómo un objeto pequeño atraviesa dos capas de codificación:
1. Inner object: {"a":1}
2. Escaped as a string: "{\"a\":1}"
3. Placed in envelope: {"payload": "{\"a\":1}"}
Cada " del objeto interno pasó a ser \", y todo el conjunto quedó envuelto en un par externo de comillas. El resultado es un único valor de cadena válido que puedes asignar a payload.
El problema con un anidamiento más profundo es que las barras invertidas se multiplican. Escapar una cadena ya escapada también escapa sus barras invertidas, así que cada capa las duplica aproximadamente: una comilla interna que era \" pasa a ser \\\" un nivel más afuera, y \\\\\" otro nivel más afuera. Un JSON-en-JSON de tres niveles es realmente difícil de leer, y por eso ayuda una herramienta. Para ir en sentido contrario y sacar de nuevo el objeto interno de la cadena, pásalo por nuestra herramienta Des-escape JSON.
Unicode y el escape \uXXXX
Por defecto, a JSON le viene bien el UTF-8 en bruto. Una é sigue siendo é, un 日 sigue siendo 日, y el documento gana en legibilidad. No necesitas escapar ningún carácter Unicode imprimible.
Entonces, ¿cuándo recurrirías a la salida \uXXXX segura para ASCII? Solo cuando no se puede confiar en que un sistema descendente maneje UTF-8: viejas pasarelas SOAP o XML, ciertas canalizaciones de registros, cabeceras de correo electrónico o archivos fuente que deben permanecer en ASCII puro. En el modo seguro para ASCII, cada carácter por encima de U+007F pasa a ser un escape \uXXXX: café se convierte en caf\u00e9. Es más ruidoso pero ASCII byte a byte, y se decodifica de vuelta al original en cualquier parser conforme.
Hay una sutileza. \uXXXX codifica una sola unidad de código UTF-16 de 16 bits, pero los caracteres fuera del Plano Multilingüe Básico (emoji, escrituras poco comunes) necesitan 21 bits. JSON los maneja con un par sustituto: dos escapes \uXXXX seguidos. Una cara sonriente 😀 (U+1F600) pasa a ser \ud83d\ude00. La mayoría de los serializadores lo hacen por ti; el peligro está en un escapador escrito a mano que emite un sustituto suelto y sin pareja.
Si los pares sustitutos y los puntos de código son territorio nuevo para ti, la Guía UTF-8 vs UTF-16 vs Unicode desglosa exactamente cómo un solo carácter se asigna a bytes y unidades de código. Es el contexto que falta detrás de por qué un emoji necesita dos escapes.
Des-escapar: leer JSON escapado de vuelta
El escape tiene su inverso. Para convertir "a\tb" de vuelta en el texto real de dos líneas o con tabulación, lo parseas: JSON.parse(str) en JavaScript, json.loads(str) en Python. El parser recorre cada secuencia de escape y reconstruye los caracteres originales, pares sustitutos incluidos.
Cuando el des-escape falla, el error casi siempre es “invalid escape sequence”, y tiene unas cuantas causas habituales:
- Una barra invertida suelta antes de un carácter que JSON no reconoce como escape, como
\q. - Un escape inventado como
\x41: JSON no tiene escape hexadecimal\x; solo usa\u. - Un escape
\utruncado con menos de cuatro dígitos hexadecimales, como\u00. - Una comilla doble suelta o sin pareja que rompe el límite de la cadena.
Comprueba que cada barra invertida inicie uno de los escapes válidos (\n \r \t \b \f \" \\ \/ \uXXXX) y que las comillas estén emparejadas. Para cadenas escapadas copiadas de en medio de una línea de registro, donde se quedaron atrás las comillas externas, nuestra herramienta Des-escape JSON acepta el cuerpo con o sin comillas alrededor y lo decodifica de cualquiera de las dos formas.
Errores comunes al escapar JSON
La mayoría de las cargas útiles rotas se remontan a uno de estos seis errores.
1. Doble escape. Escapar un texto que ya estaba escapado convierte \n en \\n y \" en \\\", así que el consumidor lee una barra invertida-n literal en lugar de un salto de línea. Esto suele ocurrir cuando un servicio anterior ya escapó el valor en JSON y tú lo vuelves a escapar. Des-escapa primero para comprobar el estado actual y luego escapa exactamente una vez.
2. Olvidar las comillas externas. Con el envoltorio desactivado obtienes solo el cuerpo escapado, no una cadena completa. Pegar hello \"world\" directamente donde se espera un valor JSON es inválido porque faltan las comillas alrededor. O mantienes el envoltorio activado o escribes tú mismo las comillas.
3. Escapar de más los caracteres no ASCII. Activar el modo seguro para ASCII cuando el consumidor maneja UTF-8 sin problemas solo infla la salida. café se convierte en caf\u00e9 sin motivo: más difícil de leer, más grande en la transmisión, cero beneficio. Déjalo desactivado salvo que un sistema heredado concreto exija ASCII puro.
4. Escapar la barra inclinada por reflejo. El escape de / importa en exactamente un lugar: JSON incrustado dentro de una etiqueta HTML <script>, donde la subcadena </script> cerraría la etiqueta antes de tiempo sin importar el contexto JSON. Escapar / a \/ la neutraliza. Fuera de ese único caso, escapar barras es puro estorbo: déjalo desactivado para cuerpos REST, archivos de configuración y cargas de mensajes.
5. Cadenas de replace improvisadas. Una canalización manual de replace('"', '\\"') casi siempre olvida algo: un carácter de control, un retroceso, un par sustituto. Usa el serializador del lenguaje, que cubre toda la especificación.
6. Escapar pero nunca des-escapar (o des-escapar dos veces). Un ciclo de ida y vuelta tiene que estar equilibrado. Escapa una vez a la entrada, des-escapa una vez a la salida. Des-escapa dos veces y destrozas las barras invertidas reales que formaban parte de los datos.
Una distinción más que conviene dejar clara: el escape de JSON no es la codificación de URL o por porcentaje. Resuelven problemas distintos para transportes distintos, y mezclarlos (codificar por porcentaje un valor y luego escaparlo en JSON, o al revés) produce un desastre que ningún parser puede leer con limpieza. La Guía de codificación y decodificación de URL explica cuándo la codificación por porcentaje es la herramienta adecuada y en qué se diferencia de lo que hace JSON.
Preguntas frecuentes
¿Qué significa escapar una cadena en JSON?
Significa reemplazar los caracteres con significado estructural para JSON, como las comillas dobles, la barra invertida y caracteres de control como el salto de línea y la tabulación, por secuencias de escape seguras como \", \\ y \n. El resultado puede incrustarse como literal de cadena dentro de un documento JSON sin romper el parseo.
¿Qué caracteres hay que escapar en JSON?
Las comillas dobles, la barra invertida, el salto de línea, el retorno de carro, la tabulación, el retroceso y el avance de página tienen cada uno un escape dedicado, y cualquier otro carácter de control por debajo de U+0020 pasa a ser \uXXXX. Los caracteres imprimibles y el UTF-8 multibyte no necesitan escaparse; la barra inclinada es opcional y solo importa dentro de etiquetas HTML <script>.
¿Es lo mismo el escape de JSON que JSON.stringify?
Son sobre todo dos vistas de una sola operación. JSON.stringify aplicado a una cadena la envuelve en comillas dobles y escapa los caracteres especiales que contiene: eso es el escape de JSON. El envoltorio activado equivale a la forma entre comillas (idéntica a JSON.stringify); el envoltorio desactivado te da solo el cuerpo escapado sin las comillas alrededor.
¿Cómo escapo una cadena para JSON en JavaScript o Python?
En JavaScript usa JSON.stringify(str); en Python usa json.dumps(str). Apóyate siempre en la función integrada en lugar de una cadena de replace escrita a mano: las integradas manejan correctamente Unicode, los caracteres de control y cada caso límite que de otro modo se te pasaría.
¿Por qué mi JSON se rompe con barras invertidas de más?
La causa habitual es el doble escape: escapar un texto que ya estaba escapado, así que \n pasa a ser \\n y el consumidor lee una barra invertida-n literal en lugar de un salto de línea. Des-escapa primero el valor para comprobar su estado real y luego escápalo exactamente una vez.
¿Necesito escapar las barras inclinadas o Unicode en JSON?
Ninguno es obligatorio. La / es un carácter normal y solo necesita escaparse cuando incrustas JSON dentro de una etiqueta HTML <script>, para evitar que la secuencia </script> la cierre antes de tiempo. Unicode se queda como UTF-8 en bruto por defecto; usa \uXXXX solo cuando un sistema descendente no pueda manejar UTF-8.