Mapeo de un objeto JSON dinámico con Jackson

1. Introducción

Trabajar con estructuras de datos JSON predefinidas con Jackson es sencillo. Sin embargo, a veces necesitamos manejar objetos JSON dinámicos , que tienen propiedades desconocidas .

En este breve tutorial, veremos varias formas de mapear objetos JSON dinámicos en clases Java.

Tenga en cuenta que en todas las pruebas, asumimos que tenemos un campo objectMapper de tipo com.fasterxml.jackson.databind.ObjectMapper .

2. Usando JsonNode

Digamos que queremos procesar las especificaciones del producto en una tienda web. Todos los productos tienen unas propiedades comunes, pero hay otras que dependen del tipo de producto.

Por ejemplo, queremos conocer la relación de aspecto de la pantalla de un teléfono celular, pero esta propiedad no tiene mucho sentido para un zapato.

La estructura de datos se ve así:

{ "name": "Pear yPhone 72", "category": "cellphone", "details": { "displayAspectRatio": "97:3", "audioConnector": "none" } }

Almacenamos las propiedades dinámicas en el objeto de detalles .

Podemos mapear las propiedades comunes con la siguiente clase de Java:

class Product { String name; String category; // standard getters and setters }

Además de eso, necesitamos una representación adecuada para el objeto de detalles . Por ejemplo, com.fasterxml.jackson.databind.JsonNode puede manejar claves dinámicas .

Para usarlo, tenemos que agregarlo como un campo a nuestra clase de Producto :

class Product { // common fields JsonNode details; // standard getters and setters }

Finalmente, verificamos que funciona:

String json = ""; Product product = objectMapper.readValue(json, Product.class); assertThat(product.getName()).isEqualTo("Pear yPhone 72"); assertThat(product.getDetails().get("audioConnector").asText()).isEqualTo("none");

Sin embargo, tenemos un problema con esta solución. Nuestra clase depende de la biblioteca de Jackson ya que tenemos un campo JsonNode .

3. Usando el mapa

Podemos resolver este problema usando java.util.Map para el campo de detalles . Más precisamente, tenemos que usar Map .

Todo lo demás puede permanecer igual:

class Product { // common fields Map details; // standard getters and setters }

Y luego podemos verificarlo con una prueba:

String json = ""; Product product = objectMapper.readValue(json, Product.class); assertThat(product.getName()).isEqualTo("Pear yPhone 72"); assertThat(product.getDetails().get("audioConnector")).isEqualTo("none");

4. Usando @JsonAnySetter

Las soluciones anteriores son buenas cuando un objeto contiene solo propiedades dinámicas. Sin embargo, a veces tenemos propiedades fijas y dinámicas mezcladas en un solo objeto JSON .

Por ejemplo, es posible que necesitemos acoplar la representación de nuestro producto:

{ "name": "Pear yPhone 72", "category": "cellphone", "displayAspectRatio": "97:3", "audioConnector": "none" }

Podemos tratar una estructura como esta como un objeto dinámico. Desafortunadamente, eso significa que no podemos definir propiedades comunes, también tenemos que tratarlas dinámicamente.

Alternativamente, podríamos usar @JsonAnySetter para marcar un método para manejar propiedades adicionales desconocidas . Dicho método debería aceptar dos argumentos: el nombre y el valor de la propiedad:

class Product { // common fields Map details = new LinkedHashMap(); @JsonAnySetter void setDetail(String key, Object value) { details.put(key, value); } // standard getters and setters }

Tenga en cuenta que tenemos que crear una instancia del objeto de detalles para evitar NullPointerExceptions .

Dado que almacenamos las propiedades dinámicas en un mapa , podemos usarlo de la misma manera que lo hacíamos antes:

String json = ""; Product product = objectMapper.readValue(json, Product.class); assertThat(product.getName()).isEqualTo("Pear yPhone 72"); assertThat(product.getDetails().get("audioConnector")).isEqualTo("none");

5. Creación de un deserializador personalizado

En la mayoría de los casos, estas soluciones funcionan bien. Sin embargo, a veces necesitamos mucho más control. Por ejemplo, podríamos almacenar información de deserialización sobre nuestros objetos JSON en una base de datos.

Podemos orientarnos a esas situaciones con un deserializador personalizado. Dado que es un tema complejo, lo cubrimos en un artículo diferente, Comenzando con la deserialización personalizada en Jackson.

6. Conclusión

En este artículo, vimos varias formas de manejar objetos JSON dinámicos con Jackson.

Como de costumbre, los ejemplos están disponibles en GitHub.