diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java index 74cdbe42f54..7aae8092886 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java @@ -18,10 +18,12 @@ package org.springframework.http.codec.json; import java.io.IOException; import java.lang.annotation.Annotation; +import java.math.BigDecimal; import java.util.List; import java.util.Map; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; @@ -106,8 +108,14 @@ public abstract class AbstractJackson2Decoder extends Jackson2CodecSupport imple @Nullable MimeType mimeType, @Nullable Map hints) { ObjectMapper mapper = getObjectMapper(); - Flux tokens = Jackson2Tokenizer.tokenize( - Flux.from(input), mapper.getFactory(), mapper, true, getMaxInMemorySize()); + + boolean forceUseOfBigDecimal = mapper.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS); + if (elementType != null && BigDecimal.class.equals(elementType.getType())) { + forceUseOfBigDecimal = true; + } + + Flux tokens = Jackson2Tokenizer.tokenize(Flux.from(input), mapper.getFactory(), mapper, + true, forceUseOfBigDecimal, getMaxInMemorySize()); ObjectReader reader = getObjectReader(elementType, hints); diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2Tokenizer.java b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2Tokenizer.java index fd31bee84f7..ff614066233 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2Tokenizer.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2Tokenizer.java @@ -27,7 +27,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.async.ByteArrayFeeder; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.deser.DefaultDeserializationContext; import com.fasterxml.jackson.databind.util.TokenBuffer; @@ -234,10 +233,13 @@ final class Jackson2Tokenizer { * @param objectMapper the current mapper instance * @param tokenizeArrays if {@code true} and the "top level" JSON object is * an array, each element is returned individually immediately after it is received + * @param forceUseOfBigDecimal if {@code true}, any floating point values encountered in source will use + * {@link java.math.BigDecimal} + * @param maxInMemorySize maximum memory size * @return the resulting token buffers */ public static Flux tokenize(Flux dataBuffers, JsonFactory jsonFactory, - ObjectMapper objectMapper, boolean tokenizeArrays, int maxInMemorySize) { + ObjectMapper objectMapper, boolean tokenizeArrays, boolean forceUseOfBigDecimal, int maxInMemorySize) { try { JsonParser parser = jsonFactory.createNonBlockingByteArrayParser(); @@ -246,7 +248,6 @@ final class Jackson2Tokenizer { context = ((DefaultDeserializationContext) context).createInstance( objectMapper.getDeserializationConfig(), parser, objectMapper.getInjectableValues()); } - boolean forceUseOfBigDecimal = objectMapper.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS); Jackson2Tokenizer tokenizer = new Jackson2Tokenizer(parser, context, tokenizeArrays, forceUseOfBigDecimal, maxInMemorySize); return dataBuffers.concatMapIterable(tokenizer::tokenize).concatWith(tokenizer.endOfInput()); diff --git a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java index dd2ba1334b2..a69c5bab3a4 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.http.codec.json; import java.io.IOException; +import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; @@ -205,6 +206,16 @@ public class Jackson2JsonDecoderTests extends AbstractDecoderTests input = stringBuffer("[ 1E+2 ]").flux(); + + testDecode(input, BigDecimal.class, step -> step + .expectNext(new BigDecimal("1E+2")) + .verifyComplete() + ); + } + private Mono stringBuffer(String value) { return Mono.defer(() -> { byte[] bytes = value.getBytes(StandardCharsets.UTF_8); diff --git a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2TokenizerTests.java b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2TokenizerTests.java index 5af6a0d3b83..501e9b4bbff 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2TokenizerTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2TokenizerTests.java @@ -25,7 +25,6 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.TreeNode; -import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.util.TokenBuffer; import org.json.JSONException; @@ -249,7 +248,8 @@ public class Jackson2TokenizerTests extends AbstractLeakCheckingTests { public void errorInStream() { DataBuffer buffer = stringBuffer("{\"id\":1,\"name\":"); Flux source = Flux.just(buffer).concatWith(Flux.error(new RuntimeException())); - Flux result = Jackson2Tokenizer.tokenize(source, this.jsonFactory, this.objectMapper, true, -1); + Flux result = Jackson2Tokenizer.tokenize(source, this.jsonFactory, this.objectMapper, true, + false, -1); StepVerifier.create(result) .expectError(RuntimeException.class) @@ -259,7 +259,8 @@ public class Jackson2TokenizerTests extends AbstractLeakCheckingTests { @Test // SPR-16521 public void jsonEOFExceptionIsWrappedAsDecodingError() { Flux source = Flux.just(stringBuffer("{\"status\": \"noClosingQuote}")); - Flux tokens = Jackson2Tokenizer.tokenize(source, this.jsonFactory, this.objectMapper, false, -1); + Flux tokens = Jackson2Tokenizer.tokenize(source, this.jsonFactory, this.objectMapper, false, + false, -1); StepVerifier.create(tokens) .expectError(DecodingException.class) @@ -269,10 +270,8 @@ public class Jackson2TokenizerTests extends AbstractLeakCheckingTests { @ParameterizedTest @ValueSource(booleans = {false, true}) public void useBigDecimalForFloats(boolean useBigDecimalForFloats) { - this.objectMapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, useBigDecimalForFloats); - Flux source = Flux.just(stringBuffer("1E+2")); - Flux tokens = Jackson2Tokenizer.tokenize(source, this.jsonFactory, this.objectMapper, false, -1); + Flux tokens = Jackson2Tokenizer.tokenize(source, this.jsonFactory, this.objectMapper, false, useBigDecimalForFloats, -1); StepVerifier.create(tokens) .assertNext(tokenBuffer -> { @@ -299,7 +298,7 @@ public class Jackson2TokenizerTests extends AbstractLeakCheckingTests { Flux tokens = Jackson2Tokenizer.tokenize( Flux.fromIterable(source).map(this::stringBuffer), - this.jsonFactory, this.objectMapper, tokenize, maxInMemorySize); + this.jsonFactory, this.objectMapper, tokenize, false, maxInMemorySize); return tokens .map(tokenBuffer -> {