Serializar y Deserializar customizado con Jackson ( bajo Jax-Rs)

Pongamos que tenemos un servicio web con Jax-Rs que acepta como parametro un objeto. Algo asi:


      @POST
      @Path("persona")
      @Consumes(MediaType.APPLICATION_JSON)
      @Produces(MediaType.APPLICATION_JSON)
      public Response createPersona(Persona per){
         
          System.out.println("aqui estoy" + per);
          //HAgo mi rollo en BBDD, etc..
           Persona personaGuardada = bbdd.save(persona);
          return Response.ok(
personaGuardada).build();
      } 


Vemos que acepta un json que intentara parsear a un objeto Persona. Para ello es necesario que tengamos definido el pojo Persona con anotaciones Jaxb:
*Nota: en la configuracion de Jaxb le hemos indicado que integre con Jackson el que se encargue de los formatos json.

@XmlRootElement(name="persona")
@XmlAccessorType(XmlAccessType.FIELD)
public class Persona {
   
    @XmlAttribute
    private Integer id;
    @XmlAttribute      
    private String name;
    @XmlAttribute
    private Date fechaCreacion;
   

    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Date getFechaCreacion() {
        return fechaCreacion;
    }
    public void setFechaCreacion(Date fechaCreacion) {
        this.fechaCreacion = fechaCreacion;
    }
}
 

Vamos a mandar un post sobre la url "\persona" con este json :

{"name":"antonio}

funcionara bien, porque el parseador no se perdera, pero si por el contrario el json tiene otros campos (por el motivo que sea) o por ejemplo vienen los nodos en mayusculas el parseo se perdera y dara error:

{"nombreUsuario":"antonio"}

Este ejemplo dara error, porque el nombre no coincide.

DESERIALIZANDO
Para estas cosas, Jackson especifica que se pueden hacer parseos customizados, que interceptan el json y permiten leerlo asi podemos manejar que queremos mapear y que no y si queremos hacer mas cosas, etc....

Es tan sencillo como extender la clase abstracta JsonDeserializer. (Es una clase parametrizada)

Quedaria algo asi:

public class CustomRequestPersonaDeserializer extends JsonDeserializer<Persona>{


Ahora, estamos obligados a sobreescribir el metodo deserialize. Este metodo nos proporciona el json de la entrada y utilidades para trabajar con ella. El metodo lo que debe devolver es el objeto que queremos formar mediante el parseo, es decir un objeto Persona.
Aqui os pongo de una manera sencilla una implementacion de como recorrer los nodos y tal y cual:

    @Override
    public Persona deserialize(JsonParser jp,
            DeserializationContext ctxt) throws IOException,
            JsonProcessingException {
        Persona resultado = new Persona();
        JsonNode rootNode = jp.getCodec().readTree(jp);
        Iterator<String> nodos = rootNode.getFieldNames();
        while(nodos.hasNext()){ //recorre los nodos del json
            String key = nodos.next();
            System.out.println("encontrado "+ key);
            if(key.equalsIgnoreCase("nombreUsuario")){//Existe el nodo
                resultado.setName(rootNode.get("nombreUsuario"));
            }
        }   
       
        return persona;
    }

Ahora faltaria indicar en la declaracion de nuestra clase, que su desarializador ( o como se diga ) es esta clase. Lo hacemos anotando la clase asi :

@JsonDeserialize(using=CustomRequestPersonaDeserializer .class)
@XmlRootElement(name="persona")
@XmlAccessorType(XmlAccessType.FIELD)
public class KuyiJsonRequest {


De esta manera, podriamos interceptar y montar el objeto persona que se pasara realmente a nuestro metodo createPersona() y rellenarlo a nuestro antojo.

SERIALIZANDO

Ahora imaginemos que vamos a la bbdd, generamos la persona y rellenamos el campo fechaCreacion con un Date o como sea.
Pues bien, cuando nos hacen una peticion por GET y devolvemos el objeto creado, pues resulta que nuestro cliente quiere la fecha en un formato especifico (por el motivo que sea), o que venga en formato letras "12 de octubre de 2008" o cualquier locura que a tu jefe se le ha ocurrido ayer por la noche(no se porque, pero esas cosas pasan.. ;-DD)

Bueno, pues es tan sencillo como crear una clase que se encargue de parsear nuestro dato java, antes de darselo al json de respuesta. Para esto hay 2 pasos:

1.Anotar el campo como que tiene que parsearse de forma especial. Voy al Pojo y añado esta anotacion en el campo:

    @JsonSerialize(using = CustomDateSerializer.class)
    @XmlAttribute
    private Date fechaCreacion;


2.Ahora implementar la clase CustomDateSerializer. Esta clase , al igual que para Deserializar, tambien tiene una clase abstracta extends JsonSerializer (tambien es una clase parametrizada) que debe extender e implementar un metodillo llamado serialize.
Como el parseo es de un objeto Date, pues quedara asi:

public class CustomDateSerializer extends JsonSerializer<Date>

Y el metodo, nos pasara el objeto Date y una referencia al json de salida para que añadamos lo que queramos. Quedara algo asi:

    @Override
    public void serialize(Date value, JsonGenerator jgen,
            SerializerProvider provider) throws IOException,
            JsonProcessingException {
        Date _date = (Date)value;
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZZZ");
        String formattedDate = formatter.format(_date);

        jgen.writeString(formattedDate);
    }

Vemos que al final lo que hacemos, no es retornar, sino escribir en el buffer del json directamente.
Estamos devolviendo el objeto Date pero en formato que nosotros queremos. En realidad en el json output se escribe un String, asi que podrias haber escrito lo que quisieras.

ahora, en todas las partes de tu codigo que se haga referecia a un Date puedes anotarlas con anotacion JsonSerialize que apunta a nuestra clase y ya todas las fechas que devuelvas iran en ese formato.


Esto que acabo de poner es extensible para todo un Objeto java, es decir, si en vez de marcar el campo Date, marcamos la clase entera como @JsonSerializer,  necesitariamos crear el custom general para la clase, de esta forma podriamos ver uno por uno los campos y como se monta el json final , etc..
Esta muy chulo para cosas customizadas.

Comparte que es un arte!


Comentarios

Entradas populares de este blog

Subir campos Blob a BBDD (Oracle) con Java

Reiniciar usuario de SVN Subversion

Cucumber y Java - definiendo test de una manera amigable