Cómo analizar archivos JSON con Python JSONPath

JSON es actualmente uno de los formatos más importantes para intercambiar datos entre aplicaciones, especialmente online. JSONPath es un lenguaje de expresión que puede utilizarse para leer datos específicos de objetos JSON. Aquí veremos la implementación de JSONPath en Python y cómo utilizarlo gracias a ejemplos sencillos de entender.

¿Qué es Python JSONPath?

JSON es un formato de archivo multisistema que puede utilizarse para facilitar el intercambio de datos estructurados entre aplicaciones. Los archivos JSON están formados por pares clave-valor enumerados. Los valores pueden aceptar distintos tipos de datos, tanto valores primitivos como objetos. Los objetos, a su vez, pueden contener sus propios pares clave-valor. Como casi todos los sistemas actuales reconocen JSON, puede utilizarse para el intercambio de datos entre cualquier tipo de aplicación, tanto localmente en el equipo como a través de Internet.

Pero no todas las aplicaciones necesitan capturar todos sus datos en un archivo JSON. En estos casos, JSONPath es una buena opción. JSONPath es un lenguaje de expresión que puede utilizarse para leer información específica de objetos JSON. En la mayoría de los lenguajes de programación, JSONPath debe importarse de una librería externa. Dado que estas librerías deben implementarse por separado para cada lenguaje, las librerías o implementaciones pueden presentar pequeñas diferencias.

El módulo jsonpath-ng de Python

“jsonpath-ng” es probablemente la implementación más común de JSONPath en Python. También hay otras implementaciones de JSONPath en Python, como “jsonpath” y “jsonpath-rw”. No obstante, estas son menos frecuentes y completas, así que aquí nos centraremos exclusivamente en “jsonpath-ng”.

Instalación

Puedes instalar “jsonpath-ng” muy fácilmente desde tu shell. Simplemente empieza introduciendo el comando “pip install jsonpath-ng”.

Nota

La instalación se realiza mediante el gestor de paquetes pip, que se utiliza por defecto para Python. En caso de que no tengas instalado este gestor de paquetes, tendrás que descargarlo primero. Puedes encontrar más información en la página web de Pip.

Sintaxis

JSONPath puede utilizarse para ejecutar consultas complejas sobre objetos JSON. Hay varios métodos, operadores y expresiones atómicas en el módulo que pueden utilizarse para seleccionar y consultar datos específicos. Los dos métodos JSONPath más importantes son “parse()” y “find()”. Con “parse()” puedes definir consultas, que luego se pueden referenciar y repetir tantas veces como quieras. Con “find()” puedes ejecutar estas consultas sobre datos JSON para extraer valores concretos. En el siguiente ejemplo se explica con más detalle.

import json
import jsonpath_ng as jp
raw_data = '''
{
    "name": "John",
    "age": 30,
    "place of residence": "New York"
}
'''
json_object = json.loads(raw_data)
name_query = jp.parse("$.name")
result = name_query.find(json_object)

print(result[0].value) # output: John 
Python

En el ejemplo anterior, los datos JSON en forma de cadena se convirtieron en un objeto diccionario utilizando “json.loads”. Este es el formato con el que Python funciona mejor. Al crear “name_query”, se definió la petición “”$.name““, que debía devolver el valor de “name”. A continuación, se aplicó al objeto JSON con “find()”. El resultado de la petición se almacenaba en la variable “result” y se leía con “result[0].value”.

Nota

Para que Python pueda leer datos JSON de una cadena o de un archivo JSON, hay que incluir el módulo de Python “json”, como en el ejemplo anterior. A partir de ahí, las cadenas y los archivos pueden convertirse a un formato legible por Python mediante “loads()” o “load()”.

El método find no solo devuelve el valor solicitado, sino también otra información contextual, como la ruta al valor buscado. Esta información se devuelve en forma de lista, donde el valor que se está buscando tiene un índice 0. Ahora puedes utilizar “result[0].value” para obtener el valor que estás buscando.

En el ejemplo anterior, se utilizó el signo de dólar al establecer la petición. Se trata de una expresión atómica que se utiliza para referirse al objeto raíz del JSON. Todos los operadores y expresiones atómicas se enumeran en la siguiente tabla.

Expresión/Operador Significado Ejemplo Explicación
$ Objeto raíz $.marcus.age Busca el valor de la clave “age” el objeto “marcus”.
. Ámbito de un objeto $.marcus Busca “marcus”, donde “marcus” es un campo del objeto raíz.
.. Búsqueda recurrente de un campo. También se buscan los campos de los subobjetos. $.people..age Devuelve todas las apariciones del campo “age” en personas y sus subobjetos.
[x] Elemento de una matriz $.people[5] Busca el sexto elemento (en el índice 5) en la matriz “people”.
\* Marcador de posición numérico, utilizado sobre todo en relación con los bucles for-loop ‘$.people[*]’ Busca un campo en “people”. Combinado con un for-loop, cada campo se devuelve sucesivamente.

Además de expresiones y operadores, existen filtros que puedes utilizar para que tu búsqueda sea aún más específica. En la implementación Python de JSONPath, estos filtros pueden ejecutarse con operadores Python. Todos los símbolos que se pueden utilizar con filtros se muestran con ejemplos en la siguiente tabla.

Símbolos Significado Ejemplo Explicación
.[?(filter)] Sintaxis general de los filtros. Se pueden omitir los paréntesis redondos. $.people[?(@.name == "Anne")] Busca personas cuyo nombre sea “Anne”.
@ Objeto que se está buscando actualmente, a menudo se utiliza en relación con los bucles for-loop. $.people[?(@.age < 50)] Busca campos en “people” cuyo valor para “age” sea inferior a 50.
<, >, <=, >=, == und != Operadores de comparación que pueden utilizarse para filtrar resultados de búsqueda específicos. $.people[@.age < 50 & @.age > 20] Busca personas que tengan entre 20 y 50 años.
& Logical AND. $.people[?(@.place of residence == Newark & @.age > 40)] Busca personas mayores de 40 años que vivan en Newark.
Nota

Si quieres utilizar filtros, tienes que incluir el módulo “jsonpath_ng.ext” y referenciarlo al llamar a “parse()”.

Caso práctico de Python JSONPath

import json
import jsonpath_ng as jp


import json
import jsonpath_ng as jp

# JSON data as string
data = """
{
    "cities": [
        {
            "name": "Trenton",
            "state": "New Jersey",
            "residents": 90048,
            "iscapital": true,
            "neighborhood Central West": {
                "residents": 1394    
            }
        },
        {
            "name": "Hamburg",
            "state": "Hamburg",
            "residents": 1841000,
            "iscapital": false
        },
        {
            "name": "New York City",
            "state": "New York",
            "residents ": 8804190
            "iscapital": false
        },
        {
            "name": "Los Angeles",
            "state": "California",
            "residents": 3898767
        }
    ]
}
"""

# Convert data from String to dictionary
json_data = json.loads(data)

# Inquiry: Names of all cities
query1 = jp.parse("cities[*].name")
for match in query1.find(json_data):
    print(match.value)     # output: Trenton, Hamburg, New York City, Los Angeles

# jsonpath_ng.ext import to apply filters
import jsonpath_ng.ext as jpx

# Anfrage: Names of all cities with less than 1.5 million residents 
query2 = jpx.parse("$.cities[?@.residents < 1500000].name")
for match in query2.find(json_data):
    print(match.value)     # output: Trenton

# All fields labeled “residents” 
query3 = jp.parse("$.cities..residents")
match = query3.find(json_data)
for i in match:
    print(i.value)     # output: 1394, 1841000, 8804190, 3898767

# The names of all cities that are not called “Trenton”
query4 = jpx.parse('$.cities[?(@.name != "Trenton")].name')
for match in query4.find(json_data):
    print(match.value)     # output: Hamburg, New York City, Los Angeles
Python

En este ejemplo, los datos JSON se especifican como una cadena y luego se convierten en un objeto diccionario mediante “loads()”. Solo hay una matriz en el objeto raíz, que a su vez contiene 4 ciudades. Cada ciudad tiene 4 campos que contienen los siguientes datos:

  • Nombre de la ciudad
  • Estado de la ciudad
  • Número de habitantes
  • Si la ciudad es capital o no

New Jersey tiene como campo adicional el llamado “Central West”, que también tiene un número de habitantes.

Una vez convertidos los datos a un formato adecuado, se ejecutan cuatro consultas diferentes. Sus funciones y resultados se dejan como comentarios en el ejemplo. Puedes observar que en la tercera consulta aparecen cinco valores. Esto se debe a que el operador “..” busca de forma recurrente los campos coincidentes. Esto significa que se buscan todos los objetos, así como todos los hijos de estos objetos. De este modo, el número de habitantes de Central West aparece junto al número de habitantes de las ciudades.

Consejo

JSON y Python, combinados, constituyen una herramienta realmente potente para la programación en Internet. Si tienes una aplicación web que quieras publicar de forma rápida, sencilla y directamente desde Git, Deploy Now de IONOS podría ser la solución perfecta.