Normalmente, cuando declaramos un parámetro de un controlador con la anotación @RequestBody
, solemos indicar la clase que contiene todos los posibles atributos del cuerpo de la petición.
En la mayoría de casos, Spring es capaz de bindear o enlazar el valor de cada elemento de la petición con su correspondiente atributo en nuestra clase, pero en ciertas ocasiones no podrá hacerlo de forma automática o puede que nosotros mismos queramos alterar este comportamiento.
Para hacer esto, existen los Converter.
Supongamos el siguiente ejemplo, tenemos un controlador que cuenta con un método de tipo POST que espera una serie de elementos en el cuerpo de la petición.
@RestController
@AllArgsConstructor
@RequestMapping("transactions")
public class CreateTransactionController {
@PostMapping
public ResponseEntity<CreateTransactionOutputDto> createTransaction(
@RequestBody CreateTransactionRequest request
) {
//
}
}
Y enviamos una petición a este endpoint con el siguiente cuerpo:
{
"reference": "AB-1234",
"iban": "ES 1234 5678 901234",
"amount": 45.67,
"description": "lorem ipsum"
}
Por defecto Spring será capaz de enlazar cada elemento con su correspondiente atributo de nuestra clase, siempre y cuando ésta tenga la siguiente forma:
public record CreateTransactionRequest(
String reference,
String iban,
Double amount,
String description
) {
//
}
Cómo mapear Value Objects
El problema aparece cuando queremos aplicar ciertos principios de DDD como los Value Objects, y representar así mejor cada tipo de dato encapsulándolo en clases como estas:
public record Reference(String value) {...}
public record Iban(String value) {...}
public record Amount(Double value) {...}
public record Description(String value) {...}
public record CreateTransactionRequest(
Reference reference,
Iban iban,
Amount amount,
Description description
) {
//
}
Ahora, Spring no encontrará la forma de mapear cada elemento con la clase correspondiente, por lo que tendremos que indicarle cómo hacerlo creando clases Converter.
En primer lugar, debemos crear el conversor para cada tipo de value object extendiendo de la clase com.fasterxml.jackson.databind.util.StdConverter
(Debemos crear un conversor por cada tipo de value object)
import com.fasterxml.jackson.databind.util.StdConverter;
public class ReferenceConverter extends StdConverter<String, Reference> { // <- Extendemos StdConverter e implementamos sus métodos
@Override
public Reference convert(String source) {
return new Reference(source); // <- Implementamos la conversión con la lógica que deseemos
}
@Override
public JavaType getInputType(TypeFactory typeFactory) {
return typeFactory.constructType(String.class); // <- Indicamos el tipo de clase de entrada
}
@Override
public JavaType getOutputType(TypeFactory typeFactory) {
return typeFactory.constructType(Reference.class); // <- Indicamos el tipo de clase de salida
}
}
Podemos ver que se trata de un código muy simple. Únicamente es necesario extender de la clase StdConverter
indicando como tipo de parámetros la clase de entrada y la de salida, en este caso indicamos que se va a convertir una String
en la clase Reference
.
Por último, debemos indicar a Spring que se utilice este conversor para mapear las instancias de la clase Reference
, incluyendo en la propia clase la correspondiente anotación:
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@JsonDeserialize(converter = ReferenceConverter.class) // <- Indicamos a Spring que use el converter correspondiente
public record Reference(String value) {...}
Ahora Spring será capaz de utilizar nuestro conversor cuando intente mapear propiedades de tipos distintos a los primitivos/wrappers o las clases para las que ya cuenta con conversores.
Top comments (0)