¿Cómo personalizar la deserialización SpringWebFlux WebClient JSON?

3 minutos de lectura

estoy usando un primavera-webflux WebClient (compilación 20170502.221452-172) para acceder a una aplicación web que produce un flujo de Entrada objetos (aplicación/stream+json) como este:

final WebClient producerClient = WebClient.create("http://localhost:8080/");

Flux<Entry> entries = producerClient.get().uri("json-stream")
        .accept(MediaType.APPLICATION_STREAM_JSON)
        .exchange()
        .flatMapMany(clientResponse -> clientResponse.bodyToFlux(Entry.class));

Si bien la deserialización del Entrada Los objetos funcionan bien para los POJO que usan tipos comunes estándar, incluidos los tipos de datos de tiempo de Java (JSR-310) como java.time.Instant, me pregunto qué tendría que hacer para agregar cualquier JSON personalizado a la deserialización de Java (por ejemplo, un Jackson ObjectMapper personalizado ).

No puedo encontrar ninguna API en cliente web o en las clases de los objetos producidos por su constructor y las API fluidas para hacerlo.

¿Alguien ha usado WebClient con deserialización personalizada?

(¿Tal vez la API aún no está allí?)

Avatar de usuario de Brian Clozel
Brian Clozel

He aquí un ejemplo que personaliza el ObjectMapper para JSON (des) serialización. Tenga en cuenta que, para fines de transmisión, se utilizan diferentes codificadores/descodificadores, pero el principio sigue siendo el mismo para su configuración.

    ExchangeStrategies strategies = ExchangeStrategies
            .builder()
            .codecs(clientDefaultCodecsConfigurer -> {
                clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(new ObjectMapper(), MediaType.APPLICATION_JSON));
                clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(new ObjectMapper(), MediaType.APPLICATION_JSON));

            }).build();

    WebClient webClient = WebClient.builder().exchangeStrategies(strategies).build();

  • ¿Podría decirnos la razón por la que Jackson2ObjectMapperBuilderCustomizer no se aplica a los códecs predeterminados?

    – hahn

    20 sep 2017 a las 19:04

  • Esto suena como una nueva pregunta para mí: ¿podría crear una?

    – Brian Clozel

    20 de septiembre de 2017 a las 19:12

  • @hahn Si usa el WebClient.Builder preconfigurado proporcionado por Spring en lugar de Webclient.builder(), puede hacer que las personalizaciones de ObjectMapper se realicen automáticamente Ref: docs.spring.io/spring-boot/docs/current/reference/html/…

    – Saisurya Kattamuri

    26 de mayo de 2020 a las 1:47


Puede configurar esto para un WebClient específico:

@Autowired
public ItunesAlbumServiceImpl(ObjectMapper mapper) {
    ExchangeStrategies strategies = ExchangeStrategies.builder().codecs(clientCodecConfigurer ->
        clientCodecConfigurer.customCodecs().decoder(
                new Jackson2JsonDecoder(mapper,
                        new MimeType("text", "javascript", StandardCharsets.UTF_8)))
    ).build();

    webClient = WebClient.builder()
            .exchangeStrategies(strategies)
            .baseUrl("https://itunes.apple.com")
            .build();
}

Pero también en el ‘nivel de aplicación’

configurando un CodecCustomizer:

@Bean
public CodecCustomizer jacksonLegacyJsonCustomizer(ObjectMapper mapper) {
    return (configurer) -> {
        MimeType textJavascript = new MimeType("text", "javascript", StandardCharsets.UTF_8);
        CodecConfigurer.CustomCodecs customCodecs = configurer.customCodecs();
        customCodecs.decoder(
                new Jackson2JsonDecoder(mapper, textJavascript));
        customCodecs.encoder(
                new Jackson2JsonEncoder(mapper, textJavascript));
    };
}

que se hará efectivo por la WebClientAutoConfiguration como un WebClient.Builder frijol:

@Autowired
public ItunesAlbumServiceImpl(WebClient.Builder webclientBuilder) {
    webClient = webclientBuilder.baseUrl("https://itunes.apple.com").build();
}

Avatar de usuario de SeverityOne
gravedad uno

Según las respuestas anteriores, terminé con este código:

final ObjectMapper mapper = new ObjectMapper()
    .findAndRegisterModules()
    .enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
final ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder()
    .codecs(configurer -> configurer.defaultCodecs()
    .jackson2JsonDecoder(new Jackson2JsonDecoder(mapper)))
    .build();
final WebClient webClient = WebClient.builder()
    .exchangeStrategies(exchangeStrategies)
    .build();

Si no incluye .findAndRegisterModules()tendrá problemas cuando quiera deserializar cosas como los objetos de tiempo de Java 8.

Configurando globalmente:

@Configuration
public class AppConfig {

    private final ObjectMapper objectMapper;

    @Autowired
    public AppConfig(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
        this.webClientBuilder = WebClient.builder()
                .exchangeStrategies(exchangeStrategies());
    }

    private ExchangeStrategies exchangeStrategies() {
        Jackson2JsonEncoder encoder = new Jackson2JsonEncoder(objectMapper);
        Jackson2JsonDecoder decoder = new Jackson2JsonDecoder(objectMapper);
        return ExchangeStrategies
                .builder()
                .codecs(configurer -> {
                    configurer.defaultCodecs().jackson2JsonEncoder(encoder);
                    configurer.defaultCodecs().jackson2JsonDecoder(decoder);
                }).build();
    }
}

desde la primavera 5.1.13 puedes usar un dedicado .codec método para personalizarlos:

WebClient.builder()
    .codecs(configurer -> {
        configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(new ObjectMapper(), MediaType.APPLICATION_JSON));
        configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(new ObjectMapper(), MediaType.APPLICATION_JSON));
     })
    .build();

  • Gracias por esto. Realmente debería actualizar la pregunta con código para versiones de Spring más recientes …

    – Martín

    18 de mayo de 2020 a las 14:17

con webflux 5.0.2, desregistrarValores predeterminados

val strategies = ExchangeStrategies.builder()
                .codecs { configurer ->
                    configurer.registerDefaults(false)
                    configurer.customCodecs().encoder(Jackson2JsonEncoder(objectMapper, APPLICATION_JSON))
                    configurer.customCodecs().decoder(Jackson2JsonDecoder(objectMapper, APPLICATION_JSON))
                }.build()

  • Gracias por esto. Realmente debería actualizar la pregunta con código para versiones de Spring más recientes …

    – Martín

    18 de mayo de 2020 a las 14:17

¿Ha sido útil esta solución?