Ir al contenido
Allin
literal dentro de un valor de cadena cerraría el elemento antes de tiempo y entregaría el resto de tus datos al analizador de HTML como marcado. Romper la secuencia con una contrabarra lo impide. En cualquier otro sitio —una respuesta de API, un fichero en disco, un mensaje en una cola— escapar barras solo infla la carga útil. La herramienta viene con la opción desactivada, que es el valor correcto, y activarla no cambia nada del significado del valor una vez analizado."}},{"@type":"Question","name":"La herramienta aceptó mi cadena pero mi analizador la rechaza. ¿Por qué?","acceptedAnswer":{"@type":"Answer","text":"Porque la dirección de desescape es deliberadamente más tolerante que un analizador de documentos, y la diferencia son los caracteres de control. Un analizador JSON rechaza un carácter de control crudo puesto literalmente dentro de una cadena —el mensaje suele decir «bad control character in string literal»—, mientras que esta herramienta lo acepta y te lo devuelve, suponiendo que estás inspeccionando un fragmento y no validando un documento. Si tu analizador protesta y la herramienta no, busca una tabulación o un salto de línea crudos que deberían haberse escrito como escape. La herramienta sí rechaza los tres defectos que hacen ilegible un fragmento: una secuencia de escape desconocida, un \\u incompleto o no hexadecimal, y una contrabarra colgando al final, y te dice la posición de cada uno."}}]},{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Allin","item":"https://allin-app-ju66.vercel.app/es"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://allin-app-ju66.vercel.app/es/blog"},{"@type":"ListItem","position":3,"name":"Escapar una cadena para JSON: tres caracteres son obligatorios y uno es una trampa","item":"https://allin-app-ju66.vercel.app/es/blog/escapar-una-cadena-para-json"}]}]

Escapar una cadena para JSON: tres caracteres son obligatorios y uno es una trampa

Publicado el 31/7/2026 · 13 min de lectura · Herramientas para desarrolladores

Daniel Okonkwo

Daniel OkonkwoDesarrollador front-end y redactor de Tecnología en Allin

Rendimiento web · Formatos de archivo

Verificado con 4 fuentes

Ver perfil
En resumen

La RFC 8259, sección 7, nombra tres cosas que DEBEN escaparse dentro de una cadena JSON y ninguna más: la comilla doble, la contrabarra y todo carácter de control de U+0000 a U+001F. Todo lo demás puede quedarse como UTF-8 literal: letras acentuadas, ideogramas, emoji, el carácter DEL en U+007F, el separador de línea en U+2028. Cualquier carácter PUEDE escaparse, y de ahí viene la costumbre de escribir \/ para una barra: JSON nunca lo exige, y la única razón para hacerlo es incrustar JSON dentro de una etiqueta script de HTML, donde la secuencia </ hay que partirla. Existen ocho escapes de dos caracteres —\" \\ \/ \b \f \n \r \t— y todo lo demás usa \uXXXX, cuatro dígitos hexadecimales, una unidad UTF-16. Un carácter fuera del Plano Multilingüe Básico necesita dos: un emoji se escribe como par sustituto de doce caracteres, nunca como escape de seis dígitos. Ahí está la trampa. Las cadenas JSON son secuencias de unidades UTF-16, y la gramática admite una unidad que es media pareja sin compañero: la especificación lo dice en la sección 8.2, con � como ejemplo. Un texto JSON así se analiza sin rechistar. Pero la sección 8.1 exige que el JSON intercambiado entre sistemas sea UTF-8, y la RFC 3629, sección 3, prohíbe a UTF-8 codificar nada entre U+D800 y U+DFFF. Medido con esta herramienta: el texto {"k":"id-�-end"} se analiza, y codificar el resultado a UTF-8 da los bytes 69 64 2d ef bf bd 2d 65 6e 64: la mitad suelta se ha convertido en U+FFFD, el carácter de reemplazo, y el valor ya no vuelve igual. Nada lanzó un error. Ese es el fallo que hay que buscar.

La RFC 8259 exige escapar exactamente tres cosas dentro de una cadena JSON. Todo lo demás es opcional. El que de verdad rompe canalizaciones es un semisustituto suelto: legal en el texto JSON, imposible en UTF-8 y sustituido en silencio en cuanto tus datos se escriben.

Tres obligatorios, todo lo demás opcional

La regla es más corta de lo que casi todo el mundo supone. Todos los caracteres Unicode pueden ir entre las comillas salvo los que deben escaparse: la propia comilla doble, la contrabarra y los caracteres de control de U+0000 a U+001F. Tres elementos. Una ruta de Windows llena de contrabarras y comillas, C:\Users\Léa\"informe".txt, exige cada contrabarra doblada y cada comilla escapada, y la é se queda tal cual. No hay regla sobre acentos, ni sobre escrituras no latinas, ni sobre emoji.

Dos límites conviene comprobarlos en vez de adivinarlos. El carácter DEL en U+007F no es un carácter de control a estos efectos —el rango se detiene en U+001F—, así que se queda literal, y pasarlo por la herramienta lo confirma: la salida es el propio carácter, un byte en UTF-8, sin escape. El separador de línea U+2028 y el de párrafo U+2029 se dejan igual, porque tampoco están en el rango U+0000 a U+001F. En su día fue un peligro real al pegar JSON dentro de un script en línea, ya que un literal de cadena de JavaScript no podía contenerlos; el lenguaje se cambió en 2019 para permitirlo. Si tu salida todavía tiene que sobrevivir a un analizador antiguo, el modo solo-ASCII de la herramienta los escapa como \u2028 y \u2029 junto con todo lo que pase del rango ASCII.

La barra escapada merece un párrafo, porque es lo más común que la gente hace sin saber por qué. JSON permite \/ y nunca lo exige. La costumbre viene de meter JSON dentro de un elemento script de HTML, donde un </script> literal dentro de una cadena cerraría el elemento antes de tiempo y entregaría el resto de tus datos al analizador de HTML; escapar la barra rompe la secuencia y el navegador no la ve nunca. La herramienta tiene un interruptor para eso, desactivado por defecto, que es el valor correcto: actívalo cuando estés incrustando, déjalo apagado en todo lo demás. Escapar barras en una respuesta de API engorda la carga útil y no cambia ni un valor.

Unidades UTF-16, no caracteres

El escape \uXXXX lleva exactamente cuatro dígitos hexadecimales, que es una unidad UTF-16 y cubre de U+0000 a U+FFFF. Todo lo que esté por encima hay que escribirlo con dos de ellos, y la especificación es explícita: para escapar un carácter extendido que no está en el Plano Multilingüe Básico, se representa como una secuencia de doce caracteres que codifica el par sustituto UTF-16. No existe forma de seis dígitos. Pasa una cara sonriente por la herramienta en modo ASCII y emite 😀, que es correcto; si alguna vez ves \u1f600 en la salida de alguien, eso no es JSON, es otra convención de escape que se coló desde Python o desde un shell.

Por eso la herramienta muestra cuatro recuentos distintos para la misma cadena y por eso discrepan. Una cara sonriente es un punto de código, dos unidades UTF-16 y cuatro bytes UTF-8. Un emoji de familia formado por cuatro figuras unidas por uniones de ancho cero son siete puntos de código, once unidades UTF-16 y veinticinco bytes UTF-8. Si tu columna de base de datos está declarada con veinte caracteres, cuál de esos tres números significa es una propiedad de la base de datos y no de tus datos, y en el hueco entre ellos viven los errores de truncamiento.

El semisustituto suelto, y cómo llega a tus datos

Un par sustituto son dos unidades de código que solo significan algo juntas: una mitad alta de U+D800 a U+DBFF seguida de una mitad baja de U+DC00 a U+DFFF. La gramática de JSON no impone el emparejamiento. La sección 8.2 de la RFC 8259 lo dice con todas las letras, señalando que la especificación permite que los valores de cadena contengan secuencias de bits que no pueden codificar caracteres Unicode, y ofreciendo � como ejemplo. Así que {"k":"id-�-end"} es un texto JSON sintácticamente válido. Cualquier analizador con el que te encuentres lo acepta.

La contradicción llega una sección antes. La sección 8.1 exige que el texto JSON intercambiado entre sistemas que no forman parte de un ecosistema cerrado se codifique en UTF-8, y la RFC 3629, que define UTF-8, prohíbe codificar números de carácter entre U+D800 y U+DFFF, precisamente porque están reservados para UTF-16. Así que un valor que JSON permite no puede expresarse en la codificación que JSON impone. Lo que hacen las implementaciones en lugar de fallar es sustituir: pasa ese texto JSON por un analizador y vuelve a codificar el resultado, y los bytes regresan como 69 64 2d ef bf bd 2d 65 6e 64. Esos tres bytes del medio, EF BF BD, son U+FFFD, el carácter de reemplazo. El valor que entró no es el que salió, y en ningún sitio se levantó un error.

Nadie teclea un semisustituto suelto. Llegan por truncamiento. Toma la cadena Rapport 📊 final, dieciséis unidades UTF-16 para quince puntos de código, y córtala a nueve unidades para que quepa en una etiqueta: el resultado es Rapport seguido de medio emoji, que la herramienta escapa como Rapport �. Ese es el origen cotidiano: una columna de base de datos con límite de caracteres, una interfaz que recorta un título, una línea de registro cortada a un ancho fijo, una importación que copia campos de longitud fija. Allí donde una cadena se corta contando unidades en vez de puntos de código, el corte puede caer en mitad de un par.

La herramienta te da dos formas de detectarlo antes de que viaje. El escapador siempre escribe un semisustituto suelto como \uXXXX, incluso en su modo por defecto no ASCII —exactamente lo que hace un JSON.stringify moderno—, así que un � o un � inesperado en la salida es la señal. Y el contador de bytes, que da los mismos números que un codificador UTF-8 real, ya habrá contado esa mitad como tres bytes: tres bytes es lo que cuesta U+FFFD, de modo que una cadena cuya forma escapada contiene un semisustituto ya está pagando por un carácter de reemplazo antes de salir de tu pantalla.

Dónde esta herramienta es deliberadamente más permisiva que un analizador

En la dirección de escapar, la salida por defecto de la herramienta coincide carácter por carácter con lo que produce un JSON.stringify moderno, en todos los casos probados: caracteres de control, emoji, acentos combinantes, rutas de Windows, semisustitutos sueltos. Si solo usas los ajustes por defecto, el resultado es el cuerpo de una cadena JSON sin las comillas exteriores, y nada más sorprendente.

En la dirección de desescapar es deliberadamente más laxa, y conviene saber dónde. Un analizador JSON de verdad rechaza un carácter de control crudo dentro de una cadena: pega un carácter de campana literal en un documento JSON y obtienes un error de sintaxis que lo dice. Esta herramienta lo acepta y te lo devuelve, con el argumento de que estás inspeccionando un fragmento y no validando un documento. Así que pasar una cadena por el modo de desescape sin quejas no demuestra que el JSON que la rodea sea válido. Sí rechaza las tres cosas que hacen absurdo un fragmento: un escape desconocido como \x, un \u incompleto o no hexadecimal, y una contrabarra colgando al final del todo, e informa de la posición y de la secuencia culpable en cada caso.

El interruptor de solo ASCII es el otro ajuste que conviene entender, porque cambia el tamaño de tu carga útil y no su significado. Escapar todo lo que pase del rango ASCII hace que la salida se pueda llevar por un sistema de codificación incierta, con un coste real: Café pasa de cuatro caracteres a nueve, y un emoji suelto de dos a doce. Úsalo cuando el transporte sea dudoso —un agregador de registros antiguo, una URL, una cabecera, una base de datos cuya intercalación no te fías— y no en otro caso, porque UTF-8 es lo que pide la sección 8.1 y ocupa menos.

Qué debe escaparse, qué puede escaparse, y qué hace realmente la herramienta con cada cosa
Entrada¿Lo exige la RFC 8259?Salida de la herramienta (modo por defecto)
Una comilla dobleSí, obligatorioEscape de dos caracteres
Una contrabarraSí, obligatorioDoblada
Una tabulación, U+0009Sí, obligatorio (carácter de control)La forma corta de la tabulación, no un escape de seis caracteres
El carácter de campana, U+0007Sí, obligatorio (control sin forma corta)Un escape de seis caracteres que acaba en 0007
DEL, U+007FNo: el rango de control acaba en U+001FSe deja literal, un byte UTF-8
Una barraNo: puede escaparse, nunca obligatorioSe deja tal cual salvo que se active la opción de la barra
Un emoji fuera del BMPNo: el UTF-8 literal valeSe conserva tal cual; en modo ASCII, un par sustituto de doce caracteres
Media pareja sustituta sueltaRepresentable, pero no codificable en UTF-8Siempre escapado como \uXXXX, incluso en modo por defecto: esa es la señal de alarma
Escapar / desescapar una cadena JSONConvierte texto en bruto en una cadena JSON válida y al revés: caracteres de control, \uXXXX, emoji y surrogates sueltos tratados igual que JSON.stringify.Probar la herramienta

Preguntas frecuentes

¿Qué caracteres debo escapar en una cadena JSON?
Exactamente tres clases, según la RFC 8259 sección 7: la comilla doble, la contrabarra y todo carácter de control de U+0000 a U+001F. Nada más es obligatorio. Letras acentuadas, ideogramas, emoji, DEL en U+007F y los separadores U+2028 y U+2029 pueden estar en la cadena como UTF-8 literal. De los caracteres de control, cinco tienen forma corta —retroceso, avance de página, salto de línea, retorno de carro y tabulación— y el resto necesitan la forma \u de seis caracteres, que es como se escribe una campana o un byte nulo. Cualquier cosa puede escaparse si quieres, y por eso a veces llega JSON perfectamente válido con cada carácter no ASCII deletreado: es más grande, no más correcto.
¿Cómo escribo un emoji en una cadena JSON?
Dos formas, y ambas correctas. Dejarlo como UTF-8 literal: nada en la especificación obliga a escaparlo, y es lo que hace la herramienta por defecto. O, si la carga útil tiene que ser ASCII, escribir el par sustituto: dos escapes \uXXXX, doce caracteres en total, uno para la mitad alta y otro para la baja. No existe forma de seis dígitos: \u1f600 no es JSON, y un analizador que lo acepte no está haciendo lo que dice la norma. El coste de la forma ASCII es real —un emoji pasa de dos caracteres a doce—, así que úsala solo cuando no puedas confiar UTF-8 al transporte.
¿Qué es un semisustituto suelto y por qué rompe cosas?
Es la mitad de una codificación de dos unidades sin compañera: una unidad de código entre U+D800 y U+DFFF por su cuenta. JSON lo permite —la RFC 8259 sección 8.2 reconoce explícitamente que los valores de cadena pueden contener secuencias de bits que no pueden codificar caracteres Unicode, y pone un sustituto sin pareja como ejemplo—, pero UTF-8 no puede expresarlo, porque la RFC 3629 prohíbe codificar nada en ese rango. Como la sección 8.1 exige UTF-8 para el intercambio, las dos reglas chocan, y lo que ocurre en la práctica es una sustitución: la mitad se convierte en U+FFFD, el carácter de reemplazo, tres bytes, y el valor original ha desaparecido. No se lanza ninguna excepción por el camino, y por eso esto aflora días después como una búsqueda que ya no encuentra nada o un identificador que ya no cruza.
¿Debo escapar la barra?
Solo cuando incrustas el JSON dentro de un elemento script de HTML. JSON permite \/ y nunca lo exige; el escape existe porque un </script> literal dentro de un valor de cadena cerraría el elemento antes de tiempo y entregaría el resto de tus datos al analizador de HTML como marcado. Romper la secuencia con una contrabarra lo impide. En cualquier otro sitio —una respuesta de API, un fichero en disco, un mensaje en una cola— escapar barras solo infla la carga útil. La herramienta viene con la opción desactivada, que es el valor correcto, y activarla no cambia nada del significado del valor una vez analizado.
La herramienta aceptó mi cadena pero mi analizador la rechaza. ¿Por qué?
Porque la dirección de desescape es deliberadamente más tolerante que un analizador de documentos, y la diferencia son los caracteres de control. Un analizador JSON rechaza un carácter de control crudo puesto literalmente dentro de una cadena —el mensaje suele decir «bad control character in string literal»—, mientras que esta herramienta lo acepta y te lo devuelve, suponiendo que estás inspeccionando un fragmento y no validando un documento. Si tu analizador protesta y la herramienta no, busca una tabulación o un salto de línea crudos que deberían haberse escrito como escape. La herramienta sí rechaza los tres defectos que hacen ilegible un fragmento: una secuencia de escape desconocida, un \u incompleto o no hexadecimal, y una contrabarra colgando al final, y te dice la posición de cada uno.

Artículos que podrían interesarte

Todas las guías

Herramientas relacionadas

Esto describe cómo se comportan un formato de archivo y un motor de renderizado, comprobado contra la especificación citada y contra el código de la herramienta tal y como está hoy. Los motores discrepan: GitHub, GitLab, un generador de sitios estáticos y la vista previa de tu editor son cuatro implementaciones distintas, y lo que funciona en una puede no funcionar en otra. Nada de esto garantiza nada sobre tu cadena de publicación: prueba el resultado donde vaya a publicarse de verdad, y trata cualquier herramienta, esta incluida, como algo que se comprueba y no como algo en lo que se confía.

Fuentes

¿Has detectado un error en este artículo?