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:
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.
Vamos a mandar un post sobre la url "\persona" con este json :
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:
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:
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:
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 :
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:
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:
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:
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!
@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..
@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();
}
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;
}
}
@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}
{"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;
}
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 {
@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;
@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);
}
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
Publicar un comentario