Browse Source

Force TokenBuffer to use BigDecimal if elementtype

This commit makes the Jackson2Tokenizer enable
TokenBuffer.forceUseOfBigDecimal if the element type given to the
Decoder is BigDecimal. Previous to this commit, values would be
converted to floats.

Closes gh-24479
pull/24494/head
Arjen Poutsma 6 years ago
parent
commit
a03a116f6b
  1. 12
      spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java
  2. 7
      spring-web/src/main/java/org/springframework/http/codec/json/Jackson2Tokenizer.java
  3. 13
      spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java
  4. 13
      spring-web/src/test/java/org/springframework/http/codec/json/Jackson2TokenizerTests.java

12
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.io.IOException;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.math.BigDecimal;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.ObjectReader;
@ -106,8 +108,14 @@ public abstract class AbstractJackson2Decoder extends Jackson2CodecSupport imple
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
ObjectMapper mapper = getObjectMapper(); ObjectMapper mapper = getObjectMapper();
Flux<TokenBuffer> 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<TokenBuffer> tokens = Jackson2Tokenizer.tokenize(Flux.from(input), mapper.getFactory(), mapper,
true, forceUseOfBigDecimal, getMaxInMemorySize());
ObjectReader reader = getObjectReader(elementType, hints); ObjectReader reader = getObjectReader(elementType, hints);

7
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.JsonToken;
import com.fasterxml.jackson.core.async.ByteArrayFeeder; import com.fasterxml.jackson.core.async.ByteArrayFeeder;
import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.DefaultDeserializationContext; import com.fasterxml.jackson.databind.deser.DefaultDeserializationContext;
import com.fasterxml.jackson.databind.util.TokenBuffer; import com.fasterxml.jackson.databind.util.TokenBuffer;
@ -234,10 +233,13 @@ final class Jackson2Tokenizer {
* @param objectMapper the current mapper instance * @param objectMapper the current mapper instance
* @param tokenizeArrays if {@code true} and the "top level" JSON object is * @param tokenizeArrays if {@code true} and the "top level" JSON object is
* an array, each element is returned individually immediately after it is received * 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 * @return the resulting token buffers
*/ */
public static Flux<TokenBuffer> tokenize(Flux<DataBuffer> dataBuffers, JsonFactory jsonFactory, public static Flux<TokenBuffer> tokenize(Flux<DataBuffer> dataBuffers, JsonFactory jsonFactory,
ObjectMapper objectMapper, boolean tokenizeArrays, int maxInMemorySize) { ObjectMapper objectMapper, boolean tokenizeArrays, boolean forceUseOfBigDecimal, int maxInMemorySize) {
try { try {
JsonParser parser = jsonFactory.createNonBlockingByteArrayParser(); JsonParser parser = jsonFactory.createNonBlockingByteArrayParser();
@ -246,7 +248,6 @@ final class Jackson2Tokenizer {
context = ((DefaultDeserializationContext) context).createInstance( context = ((DefaultDeserializationContext) context).createInstance(
objectMapper.getDeserializationConfig(), parser, objectMapper.getInjectableValues()); objectMapper.getDeserializationConfig(), parser, objectMapper.getInjectableValues());
} }
boolean forceUseOfBigDecimal = objectMapper.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
Jackson2Tokenizer tokenizer = new Jackson2Tokenizer(parser, context, tokenizeArrays, forceUseOfBigDecimal, Jackson2Tokenizer tokenizer = new Jackson2Tokenizer(parser, context, tokenizeArrays, forceUseOfBigDecimal,
maxInMemorySize); maxInMemorySize);
return dataBuffers.concatMapIterable(tokenizer::tokenize).concatWith(tokenizer.endOfInput()); return dataBuffers.concatMapIterable(tokenizer::tokenize).concatWith(tokenizer.endOfInput());

13
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@
package org.springframework.http.codec.json; package org.springframework.http.codec.json;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -205,6 +206,16 @@ public class Jackson2JsonDecoderTests extends AbstractDecoderTests<Jackson2JsonD
); );
} }
@Test
public void bigDecimalFlux() {
Flux<DataBuffer> input = stringBuffer("[ 1E+2 ]").flux();
testDecode(input, BigDecimal.class, step -> step
.expectNext(new BigDecimal("1E+2"))
.verifyComplete()
);
}
private Mono<DataBuffer> stringBuffer(String value) { private Mono<DataBuffer> stringBuffer(String value) {
return Mono.defer(() -> { return Mono.defer(() -> {
byte[] bytes = value.getBytes(StandardCharsets.UTF_8); byte[] bytes = value.getBytes(StandardCharsets.UTF_8);

13
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.JsonParser;
import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.TreeNode; import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.util.TokenBuffer; import com.fasterxml.jackson.databind.util.TokenBuffer;
import org.json.JSONException; import org.json.JSONException;
@ -249,7 +248,8 @@ public class Jackson2TokenizerTests extends AbstractLeakCheckingTests {
public void errorInStream() { public void errorInStream() {
DataBuffer buffer = stringBuffer("{\"id\":1,\"name\":"); DataBuffer buffer = stringBuffer("{\"id\":1,\"name\":");
Flux<DataBuffer> source = Flux.just(buffer).concatWith(Flux.error(new RuntimeException())); Flux<DataBuffer> source = Flux.just(buffer).concatWith(Flux.error(new RuntimeException()));
Flux<TokenBuffer> result = Jackson2Tokenizer.tokenize(source, this.jsonFactory, this.objectMapper, true, -1); Flux<TokenBuffer> result = Jackson2Tokenizer.tokenize(source, this.jsonFactory, this.objectMapper, true,
false, -1);
StepVerifier.create(result) StepVerifier.create(result)
.expectError(RuntimeException.class) .expectError(RuntimeException.class)
@ -259,7 +259,8 @@ public class Jackson2TokenizerTests extends AbstractLeakCheckingTests {
@Test // SPR-16521 @Test // SPR-16521
public void jsonEOFExceptionIsWrappedAsDecodingError() { public void jsonEOFExceptionIsWrappedAsDecodingError() {
Flux<DataBuffer> source = Flux.just(stringBuffer("{\"status\": \"noClosingQuote}")); Flux<DataBuffer> source = Flux.just(stringBuffer("{\"status\": \"noClosingQuote}"));
Flux<TokenBuffer> tokens = Jackson2Tokenizer.tokenize(source, this.jsonFactory, this.objectMapper, false, -1); Flux<TokenBuffer> tokens = Jackson2Tokenizer.tokenize(source, this.jsonFactory, this.objectMapper, false,
false, -1);
StepVerifier.create(tokens) StepVerifier.create(tokens)
.expectError(DecodingException.class) .expectError(DecodingException.class)
@ -269,10 +270,8 @@ public class Jackson2TokenizerTests extends AbstractLeakCheckingTests {
@ParameterizedTest @ParameterizedTest
@ValueSource(booleans = {false, true}) @ValueSource(booleans = {false, true})
public void useBigDecimalForFloats(boolean useBigDecimalForFloats) { public void useBigDecimalForFloats(boolean useBigDecimalForFloats) {
this.objectMapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, useBigDecimalForFloats);
Flux<DataBuffer> source = Flux.just(stringBuffer("1E+2")); Flux<DataBuffer> source = Flux.just(stringBuffer("1E+2"));
Flux<TokenBuffer> tokens = Jackson2Tokenizer.tokenize(source, this.jsonFactory, this.objectMapper, false, -1); Flux<TokenBuffer> tokens = Jackson2Tokenizer.tokenize(source, this.jsonFactory, this.objectMapper, false, useBigDecimalForFloats, -1);
StepVerifier.create(tokens) StepVerifier.create(tokens)
.assertNext(tokenBuffer -> { .assertNext(tokenBuffer -> {
@ -299,7 +298,7 @@ public class Jackson2TokenizerTests extends AbstractLeakCheckingTests {
Flux<TokenBuffer> tokens = Jackson2Tokenizer.tokenize( Flux<TokenBuffer> tokens = Jackson2Tokenizer.tokenize(
Flux.fromIterable(source).map(this::stringBuffer), Flux.fromIterable(source).map(this::stringBuffer),
this.jsonFactory, this.objectMapper, tokenize, maxInMemorySize); this.jsonFactory, this.objectMapper, tokenize, false, maxInMemorySize);
return tokens return tokens
.map(tokenBuffer -> { .map(tokenBuffer -> {

Loading…
Cancel
Save