Jugando con JSON en Java y la librería GSON. Parte 2

0
14521

Jugando con JSON en Java y la librería GSON. Parte 2

0. Índice de contenidos.

1. Introducción

Como hemos visto en el tutorial de Miguel Arlandy en Jugando con JSON en Java y la librería Gson podemos hacer montón de cosas con esta libreria, pero aún podemos hacer más!

Hay casos en los que necesitamos personalizar un poco más la serialización/deserialización ya que la traducción no es directa. Por poner un ejemplo (que usaré más adelante), quizás el receptor necesite recibir un 1 ó un 0 en vez de un true o un false.

También puede ser interesante, evitar serializar alguna propiedad de una clase. Por ejemplo, alguna propiedad en la que guardemos algún estado del objeto que sea temporal.

En este tutorial vamos a ver un ejemplo de estos dos casos, que a mí particularmente, me ha sido útil en los proyectos en los que he participado.

2. Entorno

  • Hardware
    • Mackbook Pro
      • Intel Core i7 2Ghz
      • 8GB RAM
      • 500GB HD
      • Sistema Operativo: Mac OS X (10.8.2)
  • Software
    • IntelliJ 11 Ultimate

3. Personalizar la serialización/deserialización.

Tanto para este ejemplo, como para el siguiente, vamos a utilizar el mismo modelo que ha usado Miguel en su tutorial. En este caso, en la clase SolicitudVacaciones hay una propiedad nueva «aceptadas» que usaremos para el primer ejemplo y la clase Empleado tiene otra nueva propiedad que es «campoAExcluir» que usaremos para el segundo ejemplo.

public class Empleado {  
  
    private final int id;  
  
    private final String nombre;  
  
    private final String empresa;  
  
    private final List vacaciones;  
      
    @JsonExclude  
    private String campoAExcluir = "";  
  
    public Empleado(int id, String nombre, String empresa, List vacaciones) {  
        this.id = id;  
        this.nombre = nombre;  
        this.empresa = empresa;  
        this.vacaciones = vacaciones;  
    }  
      
    //getters  
}  
public class SolicitudVacaciones {  
  
    private final Date inicio;  
  
    private final Date fin;  
  
    @SerializedName("d")  
    private final int totalDias;  
  
    //Se ha añadido el campo "aceptadas" para el ejemplo  
    private Boolean aceptadas;  
  
    public SolicitudVacaciones(Date inicio, Date fin, int totalDias) {  
        this.inicio = inicio;  
        this.fin = fin;  
        this.totalDias = totalDias;  
        this.aceptadas = Boolean.FALSE;  
    }  
      
    //gettes y setter  
}

Si serializamos una instancia de la clase SolicitudVacaciones, obtendremos algo como esto:

{  
    "inicio":"Sep 18, 2012 10:34:13 AM",  
    "fin":"Sep 18, 2012 10:34:13 AM",  
    "d":0  
    ,"aceptadas":false  
} 

Pero ¿Que ocurre si el receptor necesita recibir un 1 o un 0 en vez de true o false?. Tenemos varias opciones, como puede ser crear otro campo, hacer un wrapper de este objeto mapeando las propiedades a los campos que necesite el receptor, etc. Para mi, la opción más adecuada, es la personalización de la serialización, y esto es algo que GSon permite hacer de forma muy sencilla.

Si lo que queremos es personalizar la serialización, es decir, pasar de nuestro Objeto a JSON, debemos crearnos una clase que defina la serialización. El único requisito de la clase es que implemente JsonSerializer.

A continuación vemos la implementación de nuestro BooleanTypeAdapter

public class BooleanTypeAdapter implements JsonSerializer {  
  
    @Override  
    public JsonElement serialize(Boolean aBoolean, Type type, JsonSerializationContext jsonSerializationContext) {  
        if(aBoolean == null) {  
            return new JsonPrimitive("");  
        }  
        return new JsonPrimitive(aBoolean.booleanValue() == true ? 1 : 0);  
    }  
}  

Como veis, la cosa no puede ser más sencilla. Como la clase implementa JsonSerializer, tenemos que implementar el método serialize, que más tarde usará gson para serializar usando nuestra implementación. Si os fijáis en el cuerpo del método, lo único que hace es comprobar si el valor recibido es un nulo, que en ese caso devuelve una cadena vacía. En otro caso, mira el contenido de la variable y devuelve un 1 o un 0 según corresponda.

El segundo paso es indicar al builder, que nos construya una instancia de Gson que utilice nuestro serializador. Esto lo hacemos usando el método registerTypeAdapter indicando que serializador (TypeAdapter) queremos usar y para que tipo.

final Gson gson = new GsonBuilder().setDateFormat("dd/MM/yyyy").registerTypeAdapter(Boolean.class, new BooleanTypeAdapter()).create();

Si ejecutamos el siguiente test, veremos que nos da un bonito verde :). Fijaros que la última propiedad (aceptadas) devuelve un 0 y no un false.

    @Test  
    public void shouldSerializeCustomBoolean() {  
        final SolicitudVacaciones solicitudVacaciones = new SolicitudVacaciones(date, date, 0);  
        final Gson gson = new GsonBuilder().setDateFormat("dd/MM/yyyy").registerTypeAdapter(Boolean.class, new BooleanTypeAdapter()).create();  
        assertEquals("{\"inicio\":\"01/01/2012\",\"fin\":\"01/01/2012\",\"d\":0,\"aceptadas\":0}", gson.toJson(solicitudVacaciones));  
    }  

Si lo que necesitamos es el proceso inverso, es decir, si al deserializar necesitamos un true o un false y lo que recibimos es un 0 lo podemos hacer de la misma manera. La única diferencia es que en vez de implementar Serializer debemos implementar Deserializer. Os pongo como quedaría implementada la clase anterior añadiendo el método para deserializar.

public class BooleanTypeAdapter implements JsonSerializer, JsonDeserializer {  
      
    //implementación JsonSerializer  
    public JsonElement serialize(Boolean aBoolean, Type type, JsonSerializationContext jsonSerializationContext) {}  
      
     @Override  
    public Boolean deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException{
        int primitive = jsonElement.getAsJsonPrimitive().getAsInt();  
        return primitive == 1 ? Boolean.TRUE : Boolean.FALSE;  
    }  
      
}  

y el test que valida nuestra implementación

@Test  
    public void shouldDeSerializeCustomBoolean() {  
        final Gson gson = new GsonBuilder().setDateFormat("dd/MM/yyyy").registerTypeAdapter(Boolean.class, new BooleanTypeAdapter()).create();  
        final String solicitudVacacionesAceptadasFalse = "{\"inicio\":\"01/01/2012\",\"fin\":\"01/01/2012\",\"d\":0,\"aceptadas\":0}";  
        final String solicitudVacacionesAceptadasTrue = "{\"inicio\":\"01/01/2012\",\"fin\":\"01/01/2012\",\"d\":0,\"aceptadas\":1}";  
        assertEquals(Boolean.FALSE, gson.fromJson(solicitudVacacionesAceptadasFalse, SolicitudVacaciones.class).getAceptadas());  
        assertEquals(Boolean.TRUE, gson.fromJson(solicitudVacacionesAceptadasTrue, SolicitudVacaciones.class).getAceptadas()); 

4. Personalizar la estrategia de exclusión

Como comentaba en la introducción, hay ocasiones en los que quizás no interese serializar todas las propiedades de una clase. Por ejemplo una propiedad que guardemos un estado temporal/interno de la instancia, un valor que sea importante en el negocio de la aplicación pero no tenga sentido que se serialice.

Para conseguir evitar que serializemos propiedades, Gson permite hacerlo de manera muy sencilla. Necesitaremos crear una anotación, una clase e indicarle al constructor de Gson que utilice nuestra estrategia de exclusión.

La anotación la necesitamos para marcar las propiedades que queremos que Gson ignore.

package com.pso.samsung.rest.provider.serializer;  
  
import java.lang.annotation.ElementType;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;  
  
@Retention(RetentionPolicy.RUNTIME)  
@Target({ElementType.FIELD})  
public @interface JsonExclude {  
    //  
}  

Como vemos, la anotación no tiene nada de especial. Es una anotación que se usa en tiempo de ejecución y que está limitada a las propiedades de una clase. Esto último es importante porque Gson, al serializar un objeto, sólo mira las propiedades de la clase (no usa los getters/setters).

Una vez que tenemos nuestra anotación para marcar las propiedades, vamos a implementar nuestra estrategia de exclusión. Para ello, creamos una clase que implemente ExclusionStrategy e implementamos los dos métodos que nos proporciona la interfaz.

public class JsonExclusionEstrategy implements ExclusionStrategy{  
  
    @Override  
    public boolean shouldSkipClass(Class  clazz) {  
        return false;  
    }  
  
    @Override  
    public boolean shouldSkipField(FieldAttributes fieldAttributes) {  
        return fieldAttributes.getAnnotation(JsonExclude.class) != null;  
    }  
  
} 

Con el primer método, decidimos si queremos excluir la clase entera en el momento de la serialización. En nuestro caso como solo queremos ignorar propiedades, devolvemos siempre false. El segundo método indica si debemos excluir una propiedad. Lo que está haciendo es mirar las propiedades de la propiedad que está serializando, y si la propiedad contiene la anotación que hemos creado en el paso anterior, entonces decimos que ignore esa propiedad

Por último, al igual que en los casos anteriores, le indicamos al constructor de Gson que utilice nuestra estrategia de exclusión

final Gson gson = new GsonBuilder().setExclusionStrategies(new JsonExclusionEstrategy()).create();

Hemos anotado la propiedad campoAExcluir con la anotación que hemos creado.

public class Empleado {  
  
    private final int id;  
  
    private final String nombre;  
  
    private final String empresa;  
  
    private final List vacaciones;  
      
    @JsonExclude  
    private String campoAExcluir = "este valor nunca se va a serializar";  
  
    public Empleado(int id, String nombre, String empresa, List vacaciones) {  
        this.id = id;  
        this.nombre = nombre;  
        this.empresa = empresa;  
        this.vacaciones = vacaciones;  
    }  
      
    //getters  
}  

El siguiente test comprueba dos cosas. Primero que el objeto que vamos a serializar tiene la propiedad «campoAExcluir» con un valor inicial. La segunda es que al serializar el objeto, no aparece el contenido de la propiedad que hemos excluido.

@Test  
    public void shouldExcludeFields() {  
        final Gson gson = new GsonBuilder().setExclusionStrategies(new JsonExclusionEstrategy()).create();  
        final Empleado empleado = new Empleado(0, "Mosca de autentia", "Autentia", null);  
        assertEquals("este valor nunca se va a serializar", empleado.getCampoAExcluir());  
        assertEquals("{\"id\":0,\"nombre\":\"Mosca de autentia\",\"empresa\":\"Autentia\"}", gson.toJson(empleado));  
    } 

5. Conclusiones

Poco más que añadir a las conclusiones de Miguel

Es una librería muy sencilla de usar y muy util debido a la gran cantidad de APIs REST que hay y están surgiendo. Nos simplifica de una manera muy cómoda el tratamiento de JSON desde nuestras aplicaciones, tanto la serialización como la deserialización. Además no es especialmente pesada, la versión que hemos utilizado en el tutorial ocupa alrededor de 130kbs

Para cualquier comentario, duda o sugerencia, tenéis el formulario que aparece a continuación.

Un saludo.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad