Java y json con jackson
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]