Ir al contenido principal
CuteJSON
↑↓ Navegar Enter Seleccionar Esc Cerrar

Hoja de trucos JSONPath, con ejemplos practicos

Publicado 5 min de lectura Por Akash Kinkar Pandey

JSONPath es un lenguaje de consultas para JSON, de la misma manera que XPath lo es para XML. Fue esbozado por Stefan Goessner en 2007, implementado de manera diferente por todos los que lo necesitaban, y finalmente estandarizado como RFC 9535 en 2024. Esa historia es por que dos implementaciones de JSONPath a menudo no coinciden, y por que una hoja de trucos es mas util con resultados adjuntos que como una lista de simbolos.

Cada consulta a continuacion se ejecuta contra este documento. Puedes pegarlo en el probador JSONPath, que carga exactamente este como su ejemplo.

{
  "store": {
    "book": [
      { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 },
      { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 },
      { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99 }
    ],
    "bicycle": { "color": "red", "price": 19.95 }
  },
  "expensive": 10
}

Los selectores

Raiz. $ es todo el documento. Cada consulta comienza ahi.

Hijo por nombre. $.store.bicycle.color devuelve "red". Un punto seguido de un nombre, tantas veces como sea necesario.

Nombre entre comillas. $['store']['bicycle'] es lo mismo escrito de otra manera. Necesitas los corchetes cuando una clave tiene un espacio, un punto o un guion, o cuando empieza con un digito: $['first name'].

Comodin. $.store.bicycle.* devuelve "red" y 19.95, todos los hijos de ese objeto. $.store.book[*] devuelve los tres libros.

Indice. $.store.book[0] es el primer libro. $.store.book[-1] es el ultimo, contando desde el final.

Segmento. $.store.book[0:2] devuelve los dos primeros libros. El final es excluyente, exactamente como en Python. $.store.book[::2] toma cada segundo libro, y un paso negativo invierte: $.store.book[::-1].

Union. $.store.book[0, 2] devuelve el primer y tercer libros. $.store.bicycle['color', 'price'] selecciona dos hijos nombrados a la vez.

Descendiente. $..price devuelve cada precio en cualquier profundidad, que aqui es 8.95, 12.99, 8.99 y 19.95. Este es el selector al que mas se recurre y el que mas cuesta en un documento grande, porque visita cada valor.

Filtro. $.store.book[?@.price < 10] devuelve los dos libros mas baratos. El @ se refiere al valor que se esta probando. La RFC 9535 elimino los parentesis que usan tutoriales anteriores, por lo que tanto [?@.price < 10] como [?(@.price < 10)] aparecen en el ambito. El probador aqui acepta ambos.

Existencia. $.store.book[?@.isbn] devuelve solo el libro que tiene un campo isbn, sin compararlo con nada.

Combinando pruebas. &&, || y ! funcionan como se esperaria: $..book[?@.category == 'fiction' && @.price < 10].title devuelve "Moby Dick".

Por que un filtro que parece correcto no devuelve nada

Casi siempre, es una discrepancia de tipos. La especificacion dice que valores de diferentes tipos nunca son iguales ni estan ordenados, por lo que un precio almacenado como "8.95" con comillas no coincide con @.price < 10, y tampoco coincide con @.price == 8.95. Si un filtro no devuelve nada, verifica los tipos en el documento antes de verificar la consulta.

La segunda causa es adjuntar el filtro al nivel incorrecto. Un filtro selecciona de entre los hijos del valor al que esta adjunto, por lo que va despues del arreglo: book[?@.price < 10], no book.price[?...]. Adjuntado a un objeto, prueba cada valor de propiedad de ese objeto en su lugar.

Tercero, un valor que falta no es null. @.isbn == null coincide con un libro cuyo isbn es literalmente null, no con un libro que no tiene isbn en absoluto. Para probar ausencia, escribe !@.isbn.

Donde las implementaciones no coinciden

El articulo de 2007 fue una propuesta, no una especificacion, por lo que las librerias divergieron durante diecisiete anos. Si estas moviendo una consulta entre herramientas, estos son los lugares a verificar:

  • Los indices negativos y pasos negativos estan ampliamente soportados ahora y faltan en algunas librerias antiguas.
  • La forma de filtro con parentesis es la ortografia antigua. La RFC 9535 usa [?...] sin parentesis, y muchas herramientas aceptan solo una de las dos.
  • La RFC define las funciones length(), count(), match() y search(). Muchas implementaciones carecen de ellas o proporcionan un conjunto diferente bajo nombres diferentes.
  • La RFC otorga a los resultados un orden de documento. Algunas implementaciones no garantizan esto, particularmente para segmentos descendientes.

El probador de este sitio implementa los selectores listados anteriormente y lo indica claramente en la pagina, incluyendo una tabla de lo que soporta. Las extensiones de funciones, los absolutos dentro de un filtro, y la comparacion de dos rutas entre si no estan implementados, y usar uno de ellos produce un mensaje que nombra la caracteristica en lugar de un error de sintaxis. Esa es una decision deliberada: una herramienta que afirma cumplimiento total y luego falla silenciosamente en un caso extremo es peor que una que te dice donde esta su limite.

JSONPath o Puntero JSON

Responden preguntas diferentes.

Un Puntero JSON nombra exactamente un valor y resuelve o no resuelve. No tiene comodines ni filtros, y es precisamente por eso que JSON Schema, JSON Patch y la mayoria de los mensajes de error de validacion lo usan: una direccion sin ambiguedad es lo que necesitan. /store/book/0/title significa un lugar.

Una consulta JSONPath es una busqueda. Puede devolver muchos valores o ninguno, y puede filtrar. Usala cuando no sabes de antemano donde esta lo que buscas.

Si conoces la direccion, usa un puntero. Si estas buscando algo, usa una consulta. Ambas herramientas en este sitio reportan la linea donde se encuentra cada resultado, por lo que de cualquier manera terminas de vuelta en el documento en lugar de en una lista de resultados abstracta.

Volver a todas las entradas