Crear anotación con validación basada en JSR-380 y serialización personalizada

0
2068

Índice de contenidos

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

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