Índice de contenidos
- 1. Introducción
- 2. Entorno
- 3. Creamos el validador
- 4. Creamos el serializador
- 5. Formas de aplicar nuestro serializador
- 6. Aplicando nuestra anotación
- 7. JSON resultante
- 8. Conclusiones
- 9. Referencias
1. Introducción
Como se define en el propio proyecto, Jackson es un conjunto de herramientas de procesamiento de datos para Java, entre las que incluye su conocido parseador a JSON, esta librería serializa/deserializa entre POJO y JSON. Su uso está tan extendido que lo incorporan multitud de proyectos como dependencia, entre ellos el conocido framework spring.
Por otro lado haremos uso de la JSR (Java Specification Requests), que nos permite mediante anotaciones validar cómodamente nuestros bean en Java.
En este tutorial vamos a suponer que se domina el uso básico de ambas tecnologías, así que nos centraremos en el caso en el cual necesitamos validar un objeto de nuestro dominio siguiendo una lógica particular, es decir, no nos sirven los validadores incluidos por defecto (notNull, notEmpty, ..) y además deseamos aplicar una serialización personalizada. Este caso es bastante común si por ejemplo queremos ofuscar datos sensibles o aplicar cualquier tipo de máscara en la serialización a JSON de un bean.
A pesar de que en el tutorial tratamos la creación de un serializador personalizado si se quiere crear un deserializador sería similar solo que para este último deberíamos extender a JsonDeserialize.
2. Entorno
- El tutorial está escrito usando el siguiente entorno:
- Hardware: Portátil MacBook Pro 15′ (2.5 GHz Intel Core i7, 16GB DDR3).
- Sistema Operativo: Mac OS High Sierra 10.13.6
- Oracle Java: 1.8.0_172
3. Creamos el validador
En este caso nuestro validador simplemente hace uso de un método de utilidad que nos comprueba que el campo dni es válido.
public class DniValidator implements ConstraintValidator { @Override public boolean isValid(String value, ConstraintValidatorContext context) { DniCheck dniCheck = new DniCheck(); return dniCheck.isValid(value); } }
4. Creamos el serializador
Por motivos de seguridad deseamos que por defecto cuando se serialize un dni se transforme todo a asteriscos.
public class DniJsonSerializer extends JsonSerializer { private static final String PATTERN_DNI = "w"; private static final String ASTERISK = "*"; @Override public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException { if (value != null) { final String valueString = String.valueOf(value); gen.writeString(valueString.replaceAll(PATTERN_DNI, ASTERISK)); } } }
5. Formas de aplicar nuestro serializador
A la hora de emplear un serializador tenemos dos opciones, una básica creando nuestro serializador y la anotación asociada la cual se podrá aplicar a nivel de clase o atributo y una opción mas personalizable extendiendo a la clase SimpleModule, esta última se emplea para casos en los que queremos algo más completo como por ejemplo pasar parámetros a nuestra anotación.
5.1. Básica
Si optamos por esta opción solo añadimos la siguiente clase, donde se puede ver que empleamos la anotación @JacksonAnnotationsInside la cual nos permite el uso de meta-anotaciones con Jackson. En nuestro caso para ganar en legibilidad hemos creado una anotación @Dni la cual aplica nuestro serializador custom.
Retention(RetentionPolicy.RUNTIME) @JacksonAnnotationsInside @JsonSerialize(using = DniSerializer.class) public @interface Dni { }
5.2. Personalizable
Si queremos ir un paso más allá y no solo aplicar un serializador personalizado a dni sino que deseamos tener una anotación propia que reciba parámetros, como en nuestro caso con el parámetro obfuscate debemos usar esta otra forma.
En primer lugar nos crearemos una clase en la que extendemos BeanSerializerModifier y sobreescribimos el método changeProperties que nos permitirá cambiar en tiempo de ejecución el serializador que se emplea.
public class DniBeanSerializationModifier extends BeanSerializerModifier { @Override public List changeProperties(SerializationConfig config, BeanDescription beanDesc, List beanProperties) { final int size = beanProperties.size(); for (int i = 0; i < size; i++) { final BeanPropertyWriter writer = beanProperties.get(i); if (writer.getAnnotation(Dni.class) != null && writer.getAnnotation(Dni.class).obfuscated()) { writer.assignSerializer(new DniJsonSerializer()); } } return beanProperties; } }
En el código anterior se recorren los campos de la clase que se está serializando, si el campo lleva la notación @Dni y además está activada la opción de obfuscated de la misma, se le aplica el DniJsonSerializer creado anteriormente.
Seguidamente nos creamos un módulo el cual extienda a SimpleModule para así registrar nuestro nuevo componente de serialización.
@Component public class DniModule extends SimpleModule { @Autowired public DniModule(DniBeanSerializationModifier dniBeanSerializationModifier) { setSerializerModifier(dniBeanSerializationModifier); } }
Por último crearemos la anotación la cual aplica nuestro validador DniValidator y además cuenta con la opción activar o desactivar nuestro serializador de ofuscado.
@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = DniValidator.class) @Inherited @Documented public @interface Dni { boolean obfuscated() default true; String message() default "{code.error}"; Class[] groups() default {}; Class[] payload() default {}; }
Los otros tres parámetros son comunes a la hora de crear anotaciones de validación:
Mesagge : atributo que devuelve la clave por defecto para crear mensajes de error.
Groups : grupo de atributos que permiten especificar a qué grupos de validación pertenece nuestra restricción.
Payload : este atributo puede ser usado por los clientes de la API para añadir su propio payload personalizado a la restricción.
6. Aplicando nuestra anotación
Si deseamos aplicar nuestra anotación simplemente debemos marcar el campo en nuestro pojo a serializar de la siguiente manera.
public class Person { private String name; private Integer age; @Dni(obfuscated = true) private String dni; }
7. JSON resultante
Así sería nuestro json obtenido al serializar un objeto persona el cual cuenta entre sus atributos con un dni.
{ "name":"John Doe", "age":30, "dni":"*********" }
8. Conclusiones
El uso de jackson para serializar/deserializar a json está tan extendido que se podría considerar el estándar de facto en Java por lo que es una librería conocida pero existen usos no básicos como el anterior que necesitan darle una vuelta. Con este tutorial pretendemos dar una solución más o menos organizada a este tipo de casos.
En resumen, hemos visto cómo crear nuestro propio serializador y dos formas de emplearlo, directamente aplicado a un tipo o basado en una anotación que nos da la posibilidad de añadir opciones a la misma.
9. Referencias
Jackson Annotation Examples