Usar expresiones JSONPath para extraer datos de un JSON en Java

Escrito por el .
java planeta-codigo programacion
Comentarios

Java

Para tratar JSON en Java hay varias alternativas una de ellas es utilizar la API de bajo nivel JSON-P, otra es JSON-B que requiere construir una o varias clases de Java a las que hacer la correspondencia entre el JSON y los objetos Java. Otra alternativa es utilizar expresiones o selectores que seleccionen los datos de JSON de forma similar a como se puede hacer con XPath para el caso de XML o jQuery con los elementos de HTML.

Las expresiones de JSONPath o XPath for JSON se componen de operadores, funciones, operadores de filtrado y predicados con los que dado un JSON y una expresión permite seleccionar, extraer y transformar los datos de forma precisa. La librería JsonPath es una implementación en Java de la especificación JSONPath.

Dado el siguiente texto en JSON estos son algunos ejemplos de expresiones que seleccionan datos utilizando JsonPath.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{
    "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
            },
            {
                "category": "fiction",
                "author": "J. R. R. Tolkien",
                "title": "The Lord of the Rings",
                "isbn": "0-395-19395-8",
                "price": 22.99
            }
        ],
        "bicycle": {
            "color": "red",
            "price": 19.95
        }
    }
}

En estas expresiones por orden se obtienen los autores de los libros de la tienda, los libros de la tienda, los libros cuyo precio es menor que 10, los libros que tienen un atributo isbn, los dos primeros libros y los precios de todos los artículos incluidos los de las bicicletas. En las páginas de JSONPath y de JsonPath hay una documentación más detallada de la sintaxis de las expresiones. JSONPath dispone de un evaluador de expresiones para probar las expresiones de forma rápida.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package io.github.picodotdev.blogbitix.javajson;

...

public class Main {

    public static void main(String[] args) throws Exception {
        ...

        // JsonPath
        BufferedReader br = new BufferedReader(new InputStreamReader(Main.class.getResourceAsStream("/store.json")));
        String storeJson = br.lines().collect(Collectors.joining());
        br.close();

        ReadContext readContext = JsonPath.parse(storeJson);

        Map<String, String> expressions = new LinkedHashMap<>();
        expressions.put("authors", "$.store.book[*].author");
        expressions.put("books", "$.store.book[*]");
        expressions.put("cheap-books", "$.store.book[?(@.price < 10)]");
        expressions.put("isbn-books", "$.store.book[?(@.isbn)]");
        expressions.put("first-books", "$.store.book[:2]");
        expressions.put("prices", "$..price");

        expressions.forEach((key, expression) -> {
            Object value = readContext.read(expression);
            System.out.printf("%s: %s%n", key, value);
        });
    }

    ...
}
1
2
3
4
5
6
authors: ["Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien"]
books: [{"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},{"category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":22.99}]
cheap-books: [{"category":"reference","author":"Nigel Rees","title":"Sayings of the Century","price":8.95},{"category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99}]
isbn-books: [{"category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99},{"category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":22.99}]
first-books: [{"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}]
prices: [8.95,12.99,8.99,22.99,19.95]

Estas son las dependencias necesarias para JsonPath y como usa SLF4J varias más para redirigir las trazas a Log4j.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
...

dependencies {
    ...

    compile "com.jayway.jsonpath:json-path:2.4.0"

    runtime 'org.apache.logging.log4j:log4j-api:2.11.1'
    runtime 'org.apache.logging.log4j:log4j:2.11.1'
    runtime 'org.apache.logging.log4j:log4j-core:2.11.1'
    runtime 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.9.6'
    runtime "org.apache.logging.log4j:log4j-slf4j18-impl:2.11.1"

    ...
}

El código equivalente para extraer estos datos usando JSON-P sería más largo, complejo, difícil de mantener y de difícil compresión. Dependiendo de la cantidad de datos a seleccionar se preferirá JSON-B si son muchos o JsonPath si son pocos o hay cierta lógica de filtrado.

La librería JMESPath es una librería equivalente a JsonPath aunque utiliza otra especificación en la que cambia la sintaxis de las expresiones pero no dejan de ser similares, por el hecho de que las expresiones JsonPath siguen el estándar de XPath para XML le da algo de mayor atractivo.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./gradlew run.