DEV Community

Cover image for Conversores personalizados para @RequestBody
Ciro
Ciro

Posted on

Conversores personalizados para @RequestBody

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
  ) {
    //
  }

}
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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
) {
    //
}
Enter fullscreen mode Exit fullscreen mode

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
) {
    //
}
Enter fullscreen mode Exit fullscreen mode

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
  }

}
Enter fullscreen mode Exit fullscreen mode

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) {...}
Enter fullscreen mode Exit fullscreen mode

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)