Guía
Cómo corregir URIError: URI malformed en JavaScript
Localiza escapes porcentuales incompletos, bytes UTF-8 inválidos y sustitutos Unicode aislados que causan URI malformed.
por Tools in a Tab · Publicado el · Actualizada
Respuesta breve
URIError: URI malformed suele significar una de dos cosas: al decodificar,
hay un % incompleto o una secuencia de bytes que no es UTF-8; al codificar,
la cadena JavaScript contiene un sustituto Unicode aislado. No tapes el error
con un try/catch vacío: conserva la entrada, identifica la operación y corrige
la capa que produjo el valor.
Diagnóstico rápido
| Operación | Entrada que falla | Qué revisar |
|---|---|---|
decodeURIComponent o decodeURI |
%, %2, %GG |
Escape incompleto o no hexadecimal |
decodeURIComponent o decodeURI |
%C3%28 |
Pares hexadecimales completos, pero bytes UTF-8 inválidos |
encodeURIComponent o encodeURI |
"\uD800" |
Sustituto Unicode aislado, no un carácter completo |
Empieza por la operación de la traza del error. Decodificar una URL completa
como un componente es otro problema: %26, %3F y %23 pueden convertirse
en separadores sin lanzar ninguna excepción.
Primero distingue codificación y decodificación
decodeURIComponent y decodeURI leen secuencias %HH. Fallan si encuentran
%, %A, %GG o bytes que no forman un carácter UTF-8 válido. Por ejemplo,
%E0%A4%A está truncado y no puede terminar la secuencia iniciada.
encodeURIComponent y encodeURI trabajan con texto Unicode. Pueden fallar si
la cadena contiene una mitad de par sustituto, algo posible al cortar una
cadena UTF-16 por índice o recibir datos internos corruptos.
El codificador y decodificador URL separa los modos componente, URL completa y valor de formulario, y devuelve diagnósticos sin enviar la entrada.
Escapes porcentuales incompletos
Cada % debe ir seguido exactamente por dos dígitos hexadecimales. %20 es un
espacio y %2F representa /; %2, %XZ y un % final no son escapes
válidos. Revisa la cadena original antes de sustituir % por %25, porque esa
operación puede convertir un dato roto en el texto literal de un escape roto.
Un error frecuente aparece al interpolar un porcentaje humano, como 50%, en
una URL y luego decodificarla. Si es parte de un valor, debe codificarse como
50%25 en el límite donde se construye ese componente.
Puedes separar el error de sintaxis del error UTF-8 con esta función:
function decodeComponentStrict(input) {
const malformedEscape = input.match(/%(?![0-9A-Fa-f]{2})/);
if (malformedEscape) {
throw new URIError(
`Escape porcentual inválido en el índice ${malformedEscape.index}`,
);
}
return decodeURIComponent(input);
}
decodeComponentStrict('50%25') devuelve 50%; %2 falla en la
comprobación inicial y %C3%28 falla al decodificar UTF-8. No se sustituye ni
se descarta el dato: el llamador recibe el error para gestionarlo.
Secuencias UTF-8 inválidas
Una serie de escapes puede tener pares hexadecimales correctos y seguir siendo
inválida. decodeURIComponent('%C3%28') falla porque C3 inicia una secuencia
UTF-8 de dos bytes, pero 28 no es un byte de continuación. No decodifiques
cada %HH directamente como un carácter Latin-1: reúne los bytes y aplica
UTF-8 de forma estricta.
MDN documenta ambos casos en la referencia de URI malformed error. La solución correcta suele estar en el productor que truncó o codificó con otra tabla de caracteres.
Sustitutos Unicode aislados al codificar
JavaScript almacena cadenas como unidades UTF-16. Muchos caracteres fuera del
plano básico, incluidos emojis, usan dos unidades llamadas par sustituto. Si
cortas justo entre ambas, queda un sustituto alto o bajo aislado que no
representa ningún valor Unicode válido; encodeURI puede lanzar URIError.
Evita truncar texto con slice() por unidades cuando debas respetar
caracteres. Iterar con Array.from(text) o for…of trabaja por puntos de código,
aunque secuencias visuales complejas pueden requerir segmentación de grafemas.
También puedes comprobar text.isWellFormed() cuando el entorno lo soporte.
const text = '👋';
const broken = text.slice(0, 1);
console.log(broken.isWellFormed()); // false
console.log(encodeURIComponent(text)); // %F0%9F%91%8B
encodeURIComponent(broken); // URIError
Sustituir una unidad aislada por � cambia el dato. Puede ser una política de
visualización deliberada, pero no una reparación sin pérdida.
Método de depuración
- Registra de forma segura si el fallo ocurre al codificar o decodificar, sin exponer datos sensibles.
- Para decodificación, busca el primer
%que no tenga dos hexadecimales. - Si la sintaxis es correcta, convierte todos los escapes a bytes y valida UTF-8.
- Para codificación, comprueba que la cadena Unicode esté bien formada.
- Confirma si la función debía actuar sobre un componente o la URL completa.
No apliques varias veces decodeURIComponent hasta que deje de cambiar. Cada
capa necesita una justificación contractual y una segunda pasada puede
convertir datos codificados en separadores activos.
Parámetros: analizar primero, decodificar una vez
Usa decodeURIComponent para un valor aislado y decodeURI cuando necesites
conservar los escapes de delimitadores reservados en una URI completa. Ninguna
de las dos funciones comprueba si el destino es seguro o accesible.
Para una query, URLSearchParams ya decodifica cada valor y convierte + en
espacio. No vuelvas a decodificar su resultado por defecto:
const url = new URL('https://example.com/?q=50%25&literal=%2520');
console.log(url.searchParams.get('q')); // 50%
console.log(url.searchParams.get('literal')); // %20, texto literal tras una capa
Este parser es tolerante: puede conservar escapes rotos o sustituir bytes
UTF-8 inválidos, en lugar de lanzar URIError. Si necesitas rechazarlos,
valida el valor codificado original antes de analizarlo. La especificación
URL
define esas reglas de formulario, distintas de la decodificación URI estricta.
Prevención
Mantén valores sin codificar dentro de la aplicación y codifícalos una sola
vez al insertarlos. Usa URL y URLSearchParams para construir direcciones,
valida entradas externas antes de decodificarlas y guarda pruebas con tildes,
emoji, %, secuencias truncadas y bytes inválidos. Un error visible es más
seguro que continuar con una URL cuyo significado haya cambiado.