Gson es una pequeña librería open source creada por Google que permite convertir el contenido de objetos Java en su representación en formato JSON y viceversa, o lo que es lo mismo, serializar y deserializar objetos Java en cadenas JSON. Su principal virtud es que es muy sencilla de utilizar y además se puede emplear en aplicaciones Android por lo que es una buena opción a tener en cuenta si se tiene que trabajar con ficheros JSON de cierta complejidad que nos obligarían a escribir bastante código si se usara la API estándar y básica de JSON de bajo nivel.
Proyecto para pruebas
Vamos a crear un proyecto muy sencillo con Maven que consistirá en una simple clase Main y un par de beans. La única dependencia que usaremos será Gson.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.danielme.blog</groupId> <artifactId>gson</artifactId> <version>1.0</version> <name>gson</name> <url>https://danielme.com/2013/07/11/json-y-java-android-introduccion-a-gson/</url> <inceptionYear>2013</inceptionYear> <description>Gson - ejemplo del tutorial</description> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.mainClass>com.danielme.blog.gson.Main</project.build.mainClass> <java.version>1.6</java.version> <gson.version>2.2.4</gson.version> </properties> <developers> <developer> <id>dmedina</id> <name>Daniel Medina</name> <email>danielme_com@yahoo.com</email> <url>http://danielme.com</url> <roles> <role>developer</role> </roles> </developer> </developers> <licenses> <license> <name>GPL 3</name> <url>http://www.gnu.org/licenses/gpl-3.0.html</url> </license> </licenses> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.4</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <mainClass>${project.build.mainClass}</mainClass> </manifest> </archive> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> <encoding>${project.build.sourceEncoding}</encoding> </configuration> </plugin> <!-- mvn clean package exec:java --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.2.1</version> <executions> <execution> <goals> <goal>java</goal> </goals> </execution> </executions> <configuration> <mainClass>${project.build.mainClass}</mainClass> </configuration> </plugin> <!-- package all dependencies in one executable big jar > mvn clean package shade:shade --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> </execution> </executions> <configuration> <finalName>${project.artifactId}-${project.version}-all-deps</finalName> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>${project.build.mainClass}</mainClass> </transformer> </transformers> <!-- http://zhentao-li.blogspot.com.es/2012/06/maven-shade-plugin-invalid-signature.html --> <filters> <filter> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters> </configuration> </plugin> </plugins> </pluginManagement> </build> <dependencies> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>${gson.version}</version> </dependency> </dependencies> </project>
Los beans que usaremos carecen de getters y setters para poder comprobar que Gson utiliza directamente los atributos de las clases.
package com.danielme.blog.gson; import java.util.List; public class Pelicula { private String titulo; private short year; private String directores; private short duracion; private List<Actor> interpretes; public Pelicula() { super(); } public Pelicula(String titulo, short year, String directores, short duration, List<Actor> interpretes) { super(); this.titulo = titulo; this.year = year; this.directores = directores; this.duracion = duration; this.interpretes = interpretes; } @Override public String toString() { String s = titulo +" (" + year + ")" + " de " + directores + ", " + duracion + " minutos. Interpretada por "; boolean first = true; for (Actor actor : interpretes) { if (first) { first = false; } else { s+=", "; } s+=actor.toString(); } return s; } }
package com.danielme.blog.gson; import java.text.SimpleDateFormat; import java.util.Calendar; public class Actor { private static final SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); private String nombre; private Calendar fechaNacimiento; public Actor() { super(); } public Actor(String nombre, Calendar fechaNacimiento) { super(); this.nombre = nombre; this.fechaNacimiento = fechaNacimiento; } @Override public String toString() { return nombre + " (" + sdf.format(fechaNacimiento.getTime()) + ")"; } }
Vamos a crear también un juego de datos de pruebas con un par de películas.
package com.danielme.blog.gson; import java.util.GregorianCalendar; import java.util.LinkedList; import java.util.List; /** * * @author danielme.com * */ public class Main { public static void main(String[] args) { Actor tomhanks = new Actor("Tom Hanks", new GregorianCalendar(1956, 8, 9)); Actor halleberry = new Actor("Halle Berry", new GregorianCalendar(1966, 7, 14)); List<Actor> actores1 = new LinkedList<Actor>(); actores1.add(tomhanks); actores1.add(halleberry); Pelicula pelicula1 = new Pelicula("El atlas de las nubes", (short) 2012, "Lana Wachowski, Tom Tykwer, Andy Wachowski", (short) 172, actores1); Actor jesse = new Actor("Jesse Eisenberg", new GregorianCalendar(1983, 9, 5)); Actor andrew = new Actor("Andrew Garfield", new GregorianCalendar(1983, 7, 20)); List<Actor> actores2 = new LinkedList<Actor>(); actores2.add(jesse); actores2.add(andrew); Pelicula pelicula2 = new Pelicula("La red social", (short) 2010, "David Fincher", (short) 120, actores2); List<Pelicula> peliculas = new LinkedList<Pelicula>(); peliculas.add(pelicula1); peliculas.add(pelicula2); } }
El proyecto en Eclipse (mvn eclipse:eclipse) tendrá este aspecto:
De Java a JSON
Pasar un objeto del modelo de datos a una cadena JSON requiere sólo un par de líneas de código.
Gson gson = new Gson(); System.out.println(gson.toJson(peliculas));
El resultado será la siguiente cadena sin espacios
[{"titulo":"El atlas de las nubes","year":2012,"directores":"Lana Wachowski, Tom Tykwer, Andy Wachowski","duracion":172,"interpretes":[{"nombre":"Tom Hanks","fechaNacimiento":{"year":1956,"month":8,"dayOfMonth":9,"hourOfDay":0,"minute":0,"second":0}},{"nombre":"Halle Berry","fechaNacimiento":{"year":1966,"month":7,"dayOfMonth":14,"hourOfDay":0,"minute":0,"second":0}}]},{"titulo":"La red social","year":2010,"directores":"David Fincher","duracion":120,"interpretes":[{"nombre":"Jesse Eisenberg","fechaNacimiento":{"year":1983,"month":9,"dayOfMonth":5,"hourOfDay":0,"minute":0,"second":0}},{"nombre":"Andrew Garfield","fechaNacimiento":{"year":1983,"month":7,"dayOfMonth":20,"hourOfDay":0,"minute":0,"second":0}}]}]
Esta es la cadena óptima para su envío al ocupar menos caracteres pero no es muy legible, vamos a generarla de forma que sea más «user friendly» creando el parseador con GsonBuilder.
Gson gson = new GsonBuilder().setPrettyPrinting().create(); System.out.println(gson.toJson(peliculas));
Ahora podemos ver el resultado con más claridad
[ { "titulo": "El atlas de las nubes", "year": 2012, "directores": "Lana Wachowski, Tom Tykwer, Andy Wachowski", "duracion": 172, "interpretes": [ { "nombre": "Tom Hanks", "fechaNacimiento": { "year": 1956, "month": 8, "dayOfMonth": 9, "hourOfDay": 0, "minute": 0, "second": 0 } }, { "nombre": "Halle Berry", "fechaNacimiento": { "year": 1966, "month": 7, "dayOfMonth": 14, "hourOfDay": 0, "minute": 0, "second": 0 } } ] }, { "titulo": "La red social", "year": 2010, "directores": "David Fincher", "duracion": 120, "interpretes": [ { "nombre": "Jesse Eisenberg", "fechaNacimiento": { "year": 1983, "month": 9, "dayOfMonth": 5, "hourOfDay": 0, "minute": 0, "second": 0 } }, { "nombre": "Andrew Garfield", "fechaNacimiento": { "year": 1983, "month": 7, "dayOfMonth": 20, "hourOfDay": 0, "minute": 0, "second": 0 } } ] } ]
Veamos ahora algunas posibilidades de Gson:
-
Modelos de datos serializables
Los objetos a parsear no deben tener referencias cruzadas entre sí, por ejemplo al objeto A una referencia al objeto B y este a su vez una referencia al objeto A. Por lo tanto, no todos los modelos de datos podrán ser serializados. También vimos que esta limitación existía en el artículo Servicios Web SOAP con JAX-WS, Spring y CXF (I) : Servidor
-
Calendar y serializadores personalizados
Los Calendar han sido descompuestos en sus atributos. Si queremos otro formateo, deberemos implementar un JsonDeserializer para la clase Calendar (o para cualquier otra para la que queramos una serialización personalizada):
package com.danielme.blog.gson; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonParseException; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; public class CalendarJsonSerializer implements JsonSerializer<Calendar>, JsonDeserializer<Calendar>{ DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy"); @Override public Calendar deserialize(JsonElement json, java.lang.reflect.Type type, JsonDeserializationContext context) throws JsonParseException { Date date = null; try { date = dateFormat.parse(json.getAsJsonPrimitive().getAsString()); } catch (ParseException ex) { throw new JsonParseException(ex); } Calendar calendar = Calendar.getInstance(); calendar.setTime(date); return calendar; } @Override public JsonElement serialize(Calendar calendar, java.lang.reflect.Type type,JsonSerializationContext context) { return new JsonPrimitive(dateFormat.format(calendar.getTime())); } }
Ahora establecemos esta clase como serializadora de los Calendar
Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapter(Calendar.class, new CalendarJsonSerializer()).registerTypeAdapter(GregorianCalendar.class, new CalendarJsonSerializer()).create();
El resultado final es el esperado:
[ { "titulo": "El atlas de las nubes", "year": 2012, "directores": "Lana Wachowski, Tom Tykwer, Andy Wachowski", "duracion": 172, "interpretes": [ { "nombre": "Tom Hanks", "fechaNacimiento": "09/09/1956" }, { "nombre": "Halle Berry", "fechaNacimiento": "14/08/1966" } ] }, { "titulo": "La red social", "year": 2010, "directores": "David Fincher", "duracion": 120, "interpretes": [ { "nombre": "Jesse Eisenberg", "fechaNacimiento": "05/10/1983" }, { "nombre": "Andrew Garfield", "fechaNacimiento": "20/08/1983" } ] } ]
-
Formato de Date
Los Date,a diferencia de los Calendar, son más fáciles de formatear ya que simplemente hay que indicarle el formato al constructor del parseador con setDateFormat:
Gson gson = new GsonBuilder().setPrettyPrinting().setDateFormat("dd-MM-yyyy").create();
-
Nulos
Los atributos que sean null no serán serializados por defecto, esto es, no aparecerán en el JSON generado. Si queremos que aparezcan, hay que indicárselo al parseador con el método serializeNulls:
Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls().create();
Con esto veríamos algo así si ponemos a null el nombre de los actores:
"interpretes": [ { "nombre": null, "fechaNacimiento": "05/10/1983" }, { "nombre": null, "fechaNacimiento": "20/08/1983" }
-
Nombres de atributos
Los nombres de los atributos de la clase en el texto JSON pueden ser personalizados con la etiqueta SerializedName.
public class Pelicula { @SerializedName("p-titulo") private String titulo; @SerializedName("p-año") private short year; @SerializedName("p-direccion") private String directores; @SerializedName("p-duracion-minutos") private short duracion;
Si generamos el JSON veremos los nuevos nombres
[ { "p-titulo": "El atlas de las nubes", "p-año": 2012, "p-direccion": "Lana Wachowski, Tom Tykwer, Andy Wachowski", "p-duracion-minutos": 172, "interpretes": [ { "nombre": "Tom Hanks", "fechaNacimiento": "09/09/1956" }, { "nombre": "Halle Berry", "fechaNacimiento": "14/08/1966" } ] }, { "p-titulo": "La red social", "p-año": 2010, "p-direccion": "David Fincher", "p-duracion-minutos": 120, "interpretes": [ { "nombre": "Jesse Eisenberg", "fechaNacimiento": "05/10/1983" }, { "nombre": null, "fechaNacimiento": "20/08/1983" } ] } ]
-
Exclusión de atributos según su modificador
Por omisión, los atributos con el modificador static y transient son excluídos de la serialización en JSON. La exclusión de atributos por su modificador es configurable mediante el método excludeFieldsWithModifiers de la factoría del parseador. Por ejemplo:
Gson gson = new GsonBuilder().excludeFieldsWithModifiers(Modifier.STATIC, Modifier.FINAL).create();
-
Exclusión de atributos mediante anotaciones
Se pueden excluir atributos de la serialización y/o deserialización a JSON mediante anotaciones. En concreto, deberemos anotar con @Expose aquellos atributos que queramos incluir en la serialización/deserialización (por defecto se incluye en ambos casos si no se definen los atributos de la anotación). Asimismo, para que esta anotación sea tenida en cuenta debe indicarse al construirse el mismo parseador con excludeFieldsWithoutExposeAnnotation. Por ejemplo, vamos a utilizar sólo título y año.
@Expose @SerializedName("p-titulo") private String titulo; @Expose @SerializedName("p-año") private short year; @SerializedName("p-direccion") private String directores; @SerializedName("p-duracion-minutos") private short duracion; private List<Actor> interpretes;
Y no nos olvidamos del constructor
Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().serializeNulls().registerTypeAdapter(GregorianCalendar.class, new CalendarJsonSerializer()).create();
El resultado:
[ { "p-titulo": "El atlas de las nubes", "p-año": 2012 }, { "p-titulo": "La red social", "p-año": 2010 } ]
-
Versionado
Una funcionalidad de Gson curiosa es la posibilidad de definir un versionado en la clase a serializar con las anotaciones @Since (número de versión a partir de la que se serializa el atributo) y @Until (número de versión hasta la que se serializa el atributo). Por ejemplo
public class Actor { @Since(1.0) private String nombre; @Until(2.0) private Calendar fechaNacimiento;
Tendremos que indicar el número de versión máxima a utilizar al construir el parseador con el método setVersion.
Gson gson = new GsonBuilder().setPrettyPrinting().setVersion(2.0).serializeNulls().registerTypeAdapter(GregorianCalendar.class, new CalendarJsonSerializer()).create();
Sólo se debería serializar el nombre del actor ya que la fecha de nacimiento sólo se serializa para versiones estrictamente anteriores a la 2.0. Comprobemos el resultado:
[ { "p-titulo": "El atlas de las nubes", "p-año": 2012, "p-direccion": "Lana Wachowski, Tom Tykwer, Andy Wachowski", "p-duracion-minutos": 172, "interpretes": [ { "nombre": "Tom Hanks" }, { "nombre": "Halle Berry" } ] }, { "p-titulo": "La red social", "p-año": 2010, "p-direccion": "David Fincher", "p-duracion-minutos": 120, "interpretes": [ { "nombre": "Jesse Eisenberg" }, { "nombre": null } ] } ]
De JSON a Java
Ya hemos visto en la sección anterior las principales posibilidades de Gson, así que el pasar de JSON a Java, ya sea un JSON generado por GSON o no, se aplica exactamente lo mismo. La dificultad radica en que quizás resulte un poco más «antinatural» crear un modelo de clases a medida de un JSON ya predefinido, pero en numerosas ocasiones es lo que haremos ya que consumiremos datos proporcionados por servicios REST de terceros que escapan a nuestro control.
En el caso de que queramos hacer el camino inverso para alguno de nuestros ejemplo anteriores, simplemente usaremos la misma configuración del parseador Gson que hemos usado para serializar las clases Java y las deserializamos a partir de la cadena con JSON con el método fromJson, al que pasaremos la cadena y la clase «raíz» en la que se volcará (en nuestro ejemplo al ser una lista de objetos obtendremos un array).
Nota: hay una versión del método fromJson que admite los datos JSON en lugar de en un String en un Reader por lo que podemos utilizar directamente ficheros, por ejemplo a través de un BufferedReader.
El ejemplo final completo:
package com.danielme.blog.gson; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.LinkedList; import java.util.List; import com.google.gson.Gson; import com.google.gson.GsonBuilder; /** * * @author danielme.com * */ public class Main { public static void main(String[] args) { Actor tomhanks = new Actor("Tom Hanks", new GregorianCalendar(1956, 8, 9)); Actor halleberry = new Actor("Halle Berry", new GregorianCalendar(1966, 7, 14)); List<Actor> actores1 = new LinkedList<Actor>(); actores1.add(tomhanks); actores1.add(halleberry); Pelicula pelicula1 = new Pelicula("El atlas de las nubes", (short) 2012, "Lana Wachowski, Tom Tykwer, Andy Wachowski", (short) 172, actores1); Actor jesse = new Actor("Jesse Eisenberg", new GregorianCalendar(1983, 9, 5)); Actor andrew = new Actor("Andrew Garfield", new GregorianCalendar(1983, 7, 20)); List<Actor> actores2 = new LinkedList<Actor>(); actores2.add(jesse); actores2.add(andrew); Pelicula pelicula2 = new Pelicula("La red social", (short) 2010, "David Fincher", (short) 120, actores2); List<Pelicula> peliculas = new LinkedList<Pelicula>(); peliculas.add(pelicula1); peliculas.add(pelicula2); GsonBuilder gsonBuilder = new GsonBuilder().serializeNulls().setPrettyPrinting(); gsonBuilder.registerTypeAdapter(GregorianCalendar.class, new CalendarJsonSerializer()); gsonBuilder.registerTypeAdapter(Calendar.class, new CalendarJsonSerializer()); Gson gson = gsonBuilder.create(); String jsonString = gson.toJson(peliculas); System.out.println(jsonString); Pelicula[] peliculasJSON = gson.fromJson(jsonString, Pelicula[].class); for(Pelicula pelicula: peliculasJSON) { System.out.println(pelicula.toString()); } } }
El resultado de la ejecución:
[ { "p-titulo": "El atlas de las nubes", "p-año": 2012, "p-direccion": "Lana Wachowski, Tom Tykwer, Andy Wachowski", "p-duracion-minutos": 172, "interpretes": [ { "nombre": "Tom Hanks", "fechaNacimiento": "09/09/1956" }, { "nombre": "Halle Berry", "fechaNacimiento": "14/08/1966" } ] }, { "p-titulo": "La red social", "p-año": 2010, "p-direccion": "David Fincher", "p-duracion-minutos": 120, "interpretes": [ { "nombre": "Jesse Eisenberg", "fechaNacimiento": "05/10/1983" }, { "nombre": "Andrew Garfield", "fechaNacimiento": "20/08/1983" } ] } ] El atlas de las nubes (2012) de Lana Wachowski, Tom Tykwer, Andy Wachowski, 172 minutos. Interpretada por Tom Hanks (09/09/1956), Halle Berry (14/08/1966) La red social (2010) de David Fincher, 120 minutos. Interpretada por Jesse Eisenberg (05/10/1983), Andrew Garfield (20/08/1983)
El código de ejemplo (proyecto Maven) se encuentra disponible en GitHub. Para más información sobre cómo utilizar GitHub, consultar este artículo.
Como haces para incluir dentro del json la palabra peliculas? Por ejemplo esto:
«peliculas»: [
{
«p-titulo»: «El atlas de las nubes»,
«p-año»: 2012,
«p-direccion»: «Lana Wachowski, Tom Tykwer, Andy Wachowski»,
«p-duracion-minutos»: 172,
«interpretes»: [
{
«nombre»: «Tom Hanks»,
«fechaNacimiento»: «09/09/1956»
},
{
«nombre»: «Halle Berry»,
«fechaNacimiento»: «14/08/1966»
}
]
},
{
«p-titulo»: «La red social»,
«p-año»: 2010,
«p-direccion»: «David Fincher»,
«p-duracion-minutos»: 120,
«interpretes»: [
{
«nombre»: «Jesse Eisenberg»,
«fechaNacimiento»: «05/10/1983»
},
{
«nombre»: «Andrew Garfield»,
«fechaNacimiento»: «20/08/1983»
}
]
}
]
Según este hilo sería así:
JsonElement jsonElement = gson.toJsonTree(peliculas);
JsonObject jsonObject = new JsonObject();
jsonObject.add(«peliculas», jsonElement);
String jsonString = jsonObject.toString();
Muchas gracias por tu respuesta! !