Conversores personalizados para @RequestBody
Tags: spring, java, spanish
Published: 2022-11-03 08:00:00

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.

```java
@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:

```json
{
    "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:

```java
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:

```java
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*)

```java
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:

```java
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.