Dev Tools

JSON Pointer (RFC 6901) explicado: rutas, escapado y ejemplos

¿Qué es JSON Pointer (RFC 6901)?

JSON Pointer es un estándar diminuto pero fundamental del IETF, definido en RFC 6901, que describe una sintaxis de cadena para identificar un único valor concreto dentro de un documento JSON. Mientras que un lenguaje de consulta como JSONPath puede devolver muchas coincidencias, un JSON Pointer siempre resuelve a exactamente una ubicación — o a ninguna. Esa precisión es la razón por la que sustenta tantos otros estándares: JSON Patch (RFC 6902) usa JSON Pointer en la path de cada operación, JSON Schema lo usa en $ref y en el reporte de errores, y JSON Reference lo usa para enlazar documentos. Si alguna vez te preguntaste qué significa el /user/roles/0 de un patch, esta es la especificación que lo define.

Un JSON Pointer es una cadena formada por cero o más tokens de referencia, cada uno precedido por una barra /. Empezando en la raíz del documento, cada token selecciona un miembro más profundo: una clave de objeto por su nombre, o un elemento de array por su índice de base cero. La evaluación recorre el documento token a token hasta que aterriza en un valor o falla porque un token no existe.

La sintaxis básica: recorrer el árbol

Considera este documento:

{
  "user": {
    "name": "Ada",
    "roles": ["reader", "editor"],
    "settings": { "theme": "dark" }
  }
}

Así resuelven varios punteros sobre él:

  • /user/name"Ada"
  • /user/roles/0"reader" (primer elemento del array)
  • /user/roles/1"editor"
  • /user/settings/theme"dark"
  • /user → el objeto completo { "name": ..., "roles": ..., "settings": ... }
  • "" (la cadena vacía) → la raíz completa del documento

Ese último caso sorprende: un puntero vacío no significa "nada", significa "el documento entero". Un puntero "/" (una sola barra) es algo distinto — apunta al valor bajo una clave cuyo nombre es la cadena vacía, lo cual es legal en JSON. Estos casos límite importan porque los errores de puntero por uno son la causa más común de que un JSON Patch falle silenciosamente al aplicarse.

Escapado: por qué existen ~0 y ~1

Como la barra / separa tokens, una clave de objeto que contenga una barra sería ambigua. JSON Pointer lo resuelve con dos secuencias de escape, y el orden en que las aplicas importa:

  • ~1 representa una barra literal /
  • ~0 representa una tilde literal ~

Así, para apuntar a una clave llamada literalmente a/b, escribes /a~1b. Para una clave llamada m~n, escribes /m~0n. Al decodificar un token debes reemplazar ~1 antes que ~0 — hacerlo al revés corrompe cualquier token que contenga ~01, que debería decodificarse como ~1 pero se convertiría erróneamente en /. Esta única regla hace tropezar a casi todos los parsers de punteros escritos a mano, lo que es el mejor argumento para no escribir el tuyo: usa la misma librería que aplica tus patches. Puedes confirmar cómo interpreta una ruta una herramienta generando un diff entre dos documentos con el comparador JSON y leyendo los punteros que emite.

Índices de array y el token -

Los elementos de array se direccionan por su índice de base cero: /items/2 es el tercer elemento. JSON Pointer también define un token especial, el guion -, que se refiere al elemento (inexistente) justo después del final del array. Por sí solo, - no puede resolver a un valor — no hay nada ahí — pero JSON Patch le da significado: una operación add sobre /items/- añade al final del array. Esta es la diferencia que más confusión causa en la práctica. add sobre /items/0 inserta antes del primer elemento y desplaza todo hacia abajo; add sobre /items/- añade al final. Si llegaste aquí desde un patch que falla, este suele ser el culpable. La guía de cómo aplicar JSON Patch recorre las reglas de aplicación en cada lenguaje.

Dónde aparece JSON Pointer

Rara vez usas JSON Pointer aislado — su valor está en los estándares construidos sobre él:

  • JSON Patch (RFC 6902): la path de cada operación y el from de move/copy son JSON Pointers.
  • JSON Schema: los valores de $ref usan fragmentos de JSON Pointer (tras un #) para referenciar definiciones, y los validadores reportan la ubicación de cada error como un puntero.
  • JSON Reference: el patrón { "$ref": "#/definitions/address" } es un fragmento de URI cuyo cuerpo es un JSON Pointer.
  • Respuestas de error de API: estándares como JSON:API incluyen un pointer en los errores de validación para que el cliente sepa exactamente qué campo falló.

Como la misma sintaxis aparece en todos ellos, aprenderla una vez compensa en todo tu stack. Cuando un validador de JSON Schema reporta un error en /items/3/price, eso es un JSON Pointer, y ahora sabes que significa "el campo price del cuarto elemento".

JSON Pointer vs JSONPath: trabajos distintos

Es fácil confundir JSON Pointer con JSONPath, pero resuelven problemas opuestos. JSON Pointer es una dirección: identifica una ubicación y se usa para escribir (parchear) y referenciar. JSONPath es una consulta: selecciona potencialmente muchos valores con comodines, filtros y recursión, y se usa para leer y extraer. Usarías JSONPath para responder "dame todos los productos por debajo de 10", y JSON Pointer para decir "reemplaza el valor en exactamente esta ruta". Un buen modelo mental: JSON Pointer es una dirección postal, JSONPath es una búsqueda. Si necesitas explorar o filtrar datos, usa la herramienta de consulta JSONPath; si necesitas nombrar un único campo para actualizarlo, usa un puntero.

Errores comunes y cómo evitarlos

La mayoría de los bugs de JSON Pointer caen en unas pocas categorías:

  • Olvidar la barra inicial: user/name no es un puntero válido; debe ser /user/name. El único puntero sin barra inicial es la cadena vacía, que significa la raíz.
  • Escapar en el orden equivocado: decodifica siempre ~1 antes que ~0.
  • Asumir que las claves numéricas son índices de array: en { "0": "x" } el token 0 selecciona la clave de objeto "0", no un elemento de array — decide el contexto.
  • Tratar una ruta ausente como null: un puntero que no resuelve es un error, no un valor null. Las operaciones que requieren que el destino exista (como replace) fallarán en vez de crearlo.
  • Programar el parser a mano: las reglas de escapado y de arrays son lo bastante sutiles como para que una librería probada sea casi siempre la opción correcta.

Resolver un puntero paso a paso

El algoritmo de evaluación de la RFC 6901 es deliberadamente simple: divide el puntero por / y luego recorre el documento token a token, seleccionando cada token un hijo del valor actual. Considera este documento:

{
  "users": [
    { "name": "Ada", "roles": ["admin", "billing"] },
    { "name": "Linus", "roles": ["dev"] }
  ],
  "config/v2": { "active": true }
}

Para resolver /users/1/roles/0: empieza en el objeto raíz, selecciona users (un array), selecciona el índice 1 (el segundo elemento), selecciona roles (un array) y luego el índice 0 — obteniendo "dev". Cada paso debe tener éxito contra el tipo del valor actual; seleccionar un índice de array en un objeto, o un nombre en un array, es un error. Para direccionar la clave incómoda config/v2 hay que escapar la barra literal: el puntero es /config~1v2/active, que resuelve a true. Si la clave contuviera una tilde, como a~b, el token sería a~0b. Puedes probarlos contra tus propios datos en el formateador JSON para confirmar la estructura antes de construir un puntero.

La forma de fragmento URI

La RFC 6901 define dos representaciones equivalentes. La forma de cadena simple — /users/0/name — es la que pasas a la mayoría de librerías y la que aparece en los documentos JSON Patch. La forma de fragmento URI antepone # al puntero y codifica en porcentaje los caracteres no legales en un fragmento, de modo que el mismo puntero se convierte en #/users/0/name. Esta es la forma que ves dentro de los valores $ref de JSON Schema, como { "$ref": "#/definitions/address" }. Ambas formas direccionan la ubicación idéntica; la diferencia es puramente dónde se incrusta el puntero. Una trampa sutil: en la forma de fragmento, caracteres como el espacio se convierten en %20 además del escapado ~0/~1, así que una clave como full name se escribe #/full%20name. Cuando un $ref misteriosamente no resuelve, la codificación en porcentaje es un culpable frecuente.

Resumen

JSON Pointer es lo bastante pequeño para aprenderlo en una tarde y lo bastante importante para usarlo a diario. Direcciona exactamente un valor recorriendo el documento token a token, escapa / y ~ como ~1 y ~0, trata los índices de array como base cero con un token especial - para añadir, y resuelve la cadena vacía al documento completo. Domina esas reglas y el resto del ecosistema JSON — Patch, Schema, References y los errores estructurados de API — se lee de repente como lenguaje llano. Cuando una ruta no resuelva, ve despacio y revisa la barra inicial, el orden de escapado y si querías un índice de array o una clave de objeto. Nueve de cada diez veces, la solución es un carácter.

← Volver al Blog