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

  1. Registra de forma segura si el fallo ocurre al codificar o decodificar, sin exponer datos sensibles.
  2. Para decodificación, busca el primer % que no tenga dos hexadecimales.
  3. Si la sintaxis es correcta, convierte todos los escapes a bytes y valida UTF-8.
  4. Para codificación, comprueba que la cadena Unicode esté bien formada.
  5. 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.