3.2. JSONPath

JSONPath es a JSON lo que XPath a XML, esto es, un lenguaje que nos permite indicar qué nodos del documento desean seleccionarse. No es aún un estándar (aunque podemos consultar sus borradores), y su existencia surge por la necesidad de tener un equivalente a XPath para documentos JSON (véase el artículo original que lo propuso). Este estado de las cosas obedece a cuál ha sido la historia del intercambio de datos, a la cual se apuntó cuando se discutió por qué este módulo se denomina lenguaje de marcas[1]: las tecnologías alrededor de XML se desarrollaron cuando éste monopolizaba el intercambio de datos, de suerte que los lenguajes que han empezado a despuntar después han ido inspirándose en ellas para lograr sus funcionalidades.

Podemos resumir sus características en los siguientes puntos:

  • La sintaxis con la que se refieren las propiedades de los objetos está inspirada en lenguajes de programación como Javascript, Python o PHP.

  • Pretende emular para JSON la utilidad de XPath en el mundo XML.

  • Tiene bastante menos expresividad y más limitaciones que XPath.

  • Aunque la tecnología parece que se ha abierto hueco, aún no está madura del todo y no tiene un estándar al que deban apegarse las implementaciones existentes.

Prudencia

La riqueza semántica de JSONPath actualmente no es comparable con la de XPath en absoluto, a lo que se añade un soporte bastante parcial de los procesadores. Además, aunque hay desarrollos que han apostado por su uso (p.e. Oracle para los tipos JSON de sus bases relacionales), otros no lo han hecho, como el caso de la base de datos documental MongoDB.

3.2.1. Procesadores

Los procesadores existentes no tienen un soporte completo para la última versión del borrador. Proponemos tres:

  • JSONPath.com, que permite las pruebas online.

  • Visual Studio Code con la extensión JSONPath Extraction, que permite extraer información de un documento JSON. Para evaluar expresiones, basta con pulsar Ctrl+Shift+P y buscar por «jsonpath» para acceder a las órdenes de la extensión.

  • El paquete python3-jsonpath-ng, que incluye la orden jsonpath_ng. La orden (aunque sí la librería de Python) no soporta filtros.

3.2.2. Sintaxis

JSONPath, por analogía con algunos lenguajes de programación utiliza el punto («.») o los corchetes («[]») para separar las claves de un objeto. Tomemos para ilustrarlo un documento ya analizado:

{
   "nombre": "Pedro Martínez Alvárez",
   "edad": 32,
   "casado": true,
   "direccion": {
      "calle": "Trujillo",
      "numero": 22
   },
   "nacimiento": "1991-08-25",
   "defuncion": null,
   "hijos": [
      "Felipe",
      "Sonsoles",
      "Pablo",
      "Angustias",
      "Filomena",
      "Belisario"
   ]
}

3.2.2.1. Mapas

Como $ es la forma de referirnos al nodo raíz el valor de la propiedad centro, podemos referirla así:

$.nombre

o con la sintaxis de corchetes equivalente:

$["nombre"]

en la que, como puede verse, es necesario escribir la clave entre comillas dobles. Del mismo modo la expresión:

$.direccion

devolvería el valor de esta propiedad que no es un escalar, sino otro mapa completo con dos propiedades. Si quisiéramos acceder a una de ellas concreta, podríamos volver a usar el punto (o los corchetes indistintamente):

$.direccion.calle

Hasta ahora hemos seleccinado una propiedad dentro de un objeto, pero ¿podemos seleccionar varias? La respuesta es sí. Esto escogería dos propiedades a la vez:

$[casado, edad]

Obsérvese que no se han usado comillas. También podemos seleccionar todas:

$.direccion[*]

que seleccionaría el valor de las dos propiedades que tiene el nodo dirección.

Por último, dentro de un mapa es posible mediante la notación «..» seleccionar propiedades que no se encuentran directamente en él, sino dentro de algún objeto anidado[2]. Por ejemplo:

$..calle

selecciona cualquier propiedad «calle» del documento.

3.2.2.2. Secuencias

La expresión:

$.hijos

devuelve una secuencia, no un objeto, por lo que no tendría sentido usar el punto, sino usar una notación que nos elija uno de los elementos de la secuencia. Como en el caso de la mayoría de los lenguajes de programación, se usan corchetes que encierran un índice de posición:

$.hijos[0]

En este caso elegimos el primer hijo (Felipe), ya que:

Advertencia

A diferencia de XPath, el primer elemento se nota con 0, no con 1.

También es posible, escoger varios elementos individuales. Esto escogería el primero y el tercero:

$.hijos[0,2]

JSONPath soporta rangos inspirándose en los slices de Python:

$.hijos[1:4]

que escoge el segundo (1), el tercero (2) y el cuarto (3). Obsérvese que el quinto (4) queda excluido de la expresión del rango. En un rango, puede omitirse el límite inferior en cuyo caso se entenderá que es 0 o el límite superior, en cuyo caso se entenderá que es la longitud de la secuencia. Por tanto:

$.hijos[:3]

equivale a:

$.hijos[0:3]

y:

$.hijos[2:]

equivale a:

$.hijos[2:6]

ya que en el ejemplo hay seis hijos. Finalmente:

$.hijos[:]

escoge todos los hijos. Esto se puede escribir también como:

$.hijos[*]

También es posible, cuando se expresa un rango, indicar el paso:

$.hijos[::2]

En este caso, se escogen los hijos que ocupan posiciones pares en la secuencia. También pueden usarse números negativos, con el mismo significado que en Python[3].

3.2.2.3. Filtros

Cuando el nodo es una secuencia, no sólo pueden seleccionarse elementos por su posición, sino utilizar filtros para escoger aquellos nodos que cumplen una determinada condición. Tomemos el ejemplo de casilleros para ilustrarlo:

JSON de casilleros
{
   "centro":  "IES Pepe Botella",
   "profesores": [
      {
         "id": 1,
         "apelativo": "Pepe",
         "nombre": "José",
         "apellidos": "Suárez Lantilla",
         "departamento": "Inglés"
      },
      {
         "id": 13,
         "apelativo": "Cristina",
         "nombre": "María  Cristina",
         "apellidos": "Prieto Monagas",
         "departamento": "Biología y Geología"
      },
      {
         "id": 15,
         "apelativo": "Manolo",
         "nombre": "Manuel",
         "apellidos": "Páez Robledo",
         "departamento": "Matemáticas"

      },
      {
         "id": 17,
         "casillero": [17, 43],
         "apelativo": "Lucía",
         "nombre": "Lucía",
         "apellidos": "Gálvez Ruiz",
         "departamento": "Inglés"
      },
      {
         "id": 28,
         "casillero": [52],
         "apelativo": "Migue",
         "nombre": "Miguel Ángel",
         "apellidos": "Campos Sanchez",
         "departamento": "Historia"
      },
      {
         "id": 81,
         "casillero": [],
         "apelativo": "Vero",
         "nombre": "Verónica",
         "apellidos": "Martín Díaz",
         "departamento": "Biología"
      },
      {
         "id": 86,
         "sustituye": 15,
         "apelativo": "Roberto",
         "nombre": "Roberto",
         "apellidos": "Mínguez Torralbo"
      }
   ]
}

En este caso, puede interesarnos escoger sólo los profesores cuyo departamento sea «Matemáticas»:

$.profesores[?(@.departamento == "Matemáticas")]

Como puede observarse, el filtro se encierra dentro de los corchetes y tiene la forma ?(expr_logica), de manera que se evaluará la expresión lógica y se devolverán solo los elementos de la secuencia que devuelvan verdadero. Dentro de estas expresiones, @ es la forma de referir cada elemento de la propia secuencia, por lo que la expresión lógica escoge aquellos elementos cuya propiedad departamento (@.departamento) es igual a Matemáticas. Otro ejemplo, puede ser:

$.profesores[?(@.nombre == @.apelativo)]

que devolverá los profesores cuyo nombre coincide exactamente con su apelativo (p.e. devolverá a Luis, pero no a Verónica, puesto que la llaman Vero).

Para construir estas expresiones hay distintos operadores:

Operadores aritméticos

Operador

Significado

+

Suma aritmética o concatenación de cadenas

-

Resta

*

Multiplicación

/

División

%

Módulo (resto)

Operadores lógicos

Operador

Significado

==

Igualdad.

<=

Menor o igual.

<

Estrictamente menor.

>=

Mayor o igual.

>

Estrictamente mayor.

&&

Y lógico.

||

O lógico.

!

Negación.

Operadores de comparación

Operador

Significado

==

Igualdad.

<=

Menor o igual.

<

Estrictamente menor.

>=

Mayor o igual.

>

Estrictamente mayor.

Debemos saber, además, que:

  • No deberían efectuarse conversiones de tipos, por lo que 17 (entero) y "17" (cadena) no deberían ser iguales.

  • Sólo está definido el orden para números y cadenas (el alfabético). En consecuencia, comparar con «<» o «>» valores de otro tipo debería devolver siempre falso[4].

  • Dos secuencias son iguales cuando contienen igual número de elementos y cada elemento de la primera secuencia es igual a su correspondiente de la segunda.

  • Dos objetos son iguales cuando incluyen las mismas claves y sus correspondientes valores de éstas son identicos en uno y otro objeto.

  • Comprobar la existencia es también una expresión lógica válida, que devuelve verdadero si al menos hay un nodo, y falso, si no lo hay. Por ejemplo:

    $.profesores[?(@.casillero)]
    

    devuelve los profesores que tienen explícita su propiedad «casillero».

  • Como en el caso de Javascript, tanto las cadenas como las secuencias tienen una propiedad implícita .length que devuelve su tamaño. Por tanto:

      $.profesores[?(@.casillero && @.casillero.length > 1)]
    
    devuelve los profesores con más de un casillero.
    

    Nota

    Obsérvese que antes de hacer la comparación nos hemos asegurado de que el profesor tiene la propiedad «casillero» para evitar errores.

    El último borrador define una serie de funciones estándar para enriquecer los filtros:

    Función

    length

    Devuelve el tamaño del array, mapa o cadena

    count

    Devuelve el tamaño del array.

    match

    Concordancia completa con un patrón.

    search

    Concordancia con un patrón.

    value

    Devuelve el valor del nodo.

    Además, de permitir que se extienda la sintaxis con otras funciones adicionales que defina el procesador.

    Nota

    El soporte de los procesadores para estas funciones es precario.

Notas al pie