Java y json con jackson

De ChuWiki

Jackson es una librería java que permite convertir clases a texto JSON y viceversa. De forma similar Gson realiza el mismo trabajo, sin embargo, cuando hay clases hijas, padres, interfaces y polimorfismo en general, Gson tiene algo de soporte extra (debe descargarse por separado), y no es tan sencillo como en Jackson (necesita registrar previamente la jerarquía de clases).

Veamos aquí un ejemplo de polimorfismo con Jackson. Tienes el código completo del ejemplo en [ https://github.com/chuidiang/chuidiang-ejemplos/tree/master/JAVA_SE/src/com/chuidiang/ejemplos/jackson Github]

Dependencia Maven[editar]

Para tener jackson en nuestro proyecto maven, la dependencia que hay que poner es esta

<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-databind</artifactId>
	<version>2.8.3</version>
	<scope>compile</scope>
</dependency>

Convertir a JSON[editar]

Para convertir nuestras clases a JSON, necesitamos crear un ObjectMapper de Jackson y luego simplemente usarlo. Tiene muchos métodos para ello, pero los fáciles serían estos

// Creamos el ObjectMapper por defecto
ObjectMapper theBadMapper = new ObjectMapper();

// Creamos la clase que queramos convertir a JSON, por ejemplo, un ArrayList de AnInterface
ArrayList<AnInterface> childs = .... 

// Convertir a JSON
String theJsonText = theBadMapper.writeValueAsString(childs);

// Volver a obtener la clase original
ArrayList<AnInterface> reconstructedChilds = theBadMapper.readValue(
    theJsonText, 
    new TypeReference<ArrayList<AnInterface>>() {} );

No hay mucho truco. Se crea un ObjectMapper con la configuración por defecto y se usa su método writeValueAsString() para obtener el texto JSON correspondiente a esa clase.

Para recuperar el objeto, podemos usar el método readValue() al que pasamos el texto JSON obtenido anteriormente y una instancia de TypeReference indicando qué tipo queremos que nos devuelva, un ArrayList<AnInterface> en nuestro ejemplo.

Con clases normales (sin herencias), todo esto funciona estupendamente.

Problema con herencias[editar]

Hay sin embargo un problema cuando hay herencias, sobre todo interfaces y clases abstractas. En nuestro ejemplo, AnInterface es una interface y de ella hay una clase que la implementa que hemos llamado AParentClass y de ella hemos hecho un par de hijos AChildClass y AnotherChildClass. Y hemos sido malos y rellenamos el ArrayList así :

ArrayList<AnInterface> childs = new ArrayList<>(2);
childs.add(new AChildClass());
childs.add(new AnotherChildClass());

es decir, decimos que en el ArrayList se van a guardar interfaces, y metemos en él dos clases concretas que implementan esa interface. Cuando ejecutamos nuestro programa anterior con el ObjectMapper por defecto e intentamos recuperar las clases a partir del JSON, obtenemos una excepción que dice

Can not construct instance of com.chuidiang.ejemplos.jackson.AnInterface

es decir, como a ObjectMapper le decimos que el ArrayList es de una interface AnInterface, intenta instanciarla para rellenar el código. Y las interfaces no pueden instanciarse, así que error al canto.

ObjectMapper con polimorfismo[editar]

La forma fácil de solucionar esto con Jackson, sin necesidad de poner anotaciones ni registrar el árbol de herencias, consiste en decirle a Jackson que cuando obtenga el JSON ponga una marca con cada clase concreta que ha convertido en JSON. El siguiente código hace esto

ArrayList<AnInterface> childs = new ArrayList<>(2);
childs.add(new AChildClass());
childs.add(new AnotherChildClass());
...
ObjectMapper theGoodMapper = new ObjectMapper();
theGoodMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

String theJsonText = theGoodMapper.writeValueAsString(childs);
System.out.println(theJsonText);

y el JSON que obtenemos tiene esta pinta

["java.util.ArrayList", [
	["com.chuidiang.ejemplos.jackson.AChildClass", {
		"anAttribute": 0,
		"anotherAttribute": null,
		"aChildAttribute": null
	}],
	["com.chuidiang.ejemplos.jackson.AnotherChildClass", {
		"anAttribute": 0,
		"anotherAttribute": null,
		"anotherChildAttribute": 0.0
	}]
]]

Vemos que ha puesto que por cada clase java que convierte a JSON, lo hace como un array de dos elementos JSON. El primero indica el nombre de la clase concreta y el segundo su contenido. Así, tenemos el ArrayList

["java.util.ArrayList", [ el contenido del arrayList ]]

y cada elemento del ArrayList es a su vez un array con dos elementos

["com.chuidiang.ejemplos.jackson.AChildClass", { el contenido de AChildClass }]

De esta forma, el ObjectMapper luego es capaz de volver a obtener las clases originales de forma fácil

ArrayList<AnInterface> reconstructedChilds = theGoodMapper.readValue(
    theJsonText, new TypeReference<ArrayList<AnInterface>>() {});
System.out.println("The good mapper works ! ");
System.out.println(reconstructedChilds);

Esto funciona bien, sin excepciones, y la última salida por pantalla nos muestra que ha construido las clases correctas

The good mapper works ! 
[com.chuidiang.ejemplos.jackson.AChildClass@5622fdf, com.chuidiang.ejemplos.jackson.AnotherChildClass@4883b407]