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()ysearch(). 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.