Force TokenBuffer to use BigDecimal if necessary
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
This commit is contained in:
@@ -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.
|
||||
@@ -18,11 +18,13 @@ 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.JsonFactory;
|
||||
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;
|
||||
@@ -113,8 +115,13 @@ public abstract class AbstractJackson2Decoder extends Jackson2CodecSupport imple
|
||||
public Flux<Object> decode(Publisher<DataBuffer> input, ResolvableType elementType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
Flux<TokenBuffer> tokens = Jackson2Tokenizer.tokenize(
|
||||
Flux.from(input), this.jsonFactory, getObjectMapper(), true, getMaxInMemorySize());
|
||||
boolean forceUseOfBigDecimal = getObjectMapper().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), this.jsonFactory, getObjectMapper(),
|
||||
true, forceUseOfBigDecimal, getMaxInMemorySize());
|
||||
return decodeInternal(tokens, elementType, mimeType, hints);
|
||||
}
|
||||
|
||||
@@ -122,8 +129,12 @@ public abstract class AbstractJackson2Decoder extends Jackson2CodecSupport imple
|
||||
public Mono<Object> decodeToMono(Publisher<DataBuffer> input, ResolvableType elementType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
Flux<TokenBuffer> tokens = Jackson2Tokenizer.tokenize(
|
||||
Flux.from(input), this.jsonFactory, getObjectMapper(), false, getMaxInMemorySize());
|
||||
boolean forceUseOfBigDecimal = getObjectMapper().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), this.jsonFactory, getObjectMapper(),
|
||||
false, forceUseOfBigDecimal, getMaxInMemorySize());
|
||||
return decodeInternal(tokens, elementType, mimeType, hints).singleOrEmpty();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -232,10 +231,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<TokenBuffer> tokenize(Flux<DataBuffer> dataBuffers, JsonFactory jsonFactory,
|
||||
ObjectMapper objectMapper, boolean tokenizeArrays, int maxInMemorySize) {
|
||||
ObjectMapper objectMapper, boolean tokenizeArrays, boolean forceUseOfBigDecimal, int maxInMemorySize) {
|
||||
|
||||
try {
|
||||
JsonParser parser = jsonFactory.createNonBlockingByteArrayParser();
|
||||
@@ -244,7 +246,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.flatMap(tokenizer::tokenize, Flux::error, tokenizer::endOfInput);
|
||||
|
||||
@@ -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;
|
||||
@@ -42,13 +43,21 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.Pojo;
|
||||
import org.springframework.util.MimeType;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
import static java.util.Collections.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.core.ResolvableType.*;
|
||||
import static org.springframework.http.MediaType.*;
|
||||
import static org.springframework.http.codec.json.Jackson2JsonDecoder.*;
|
||||
import static org.springframework.http.codec.json.JacksonViewBean.*;
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.springframework.core.ResolvableType.forClass;
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8;
|
||||
import static org.springframework.http.MediaType.APPLICATION_STREAM_JSON;
|
||||
import static org.springframework.http.MediaType.APPLICATION_XML;
|
||||
import static org.springframework.http.codec.json.Jackson2JsonDecoder.JSON_VIEW_HINT;
|
||||
import static org.springframework.http.codec.json.JacksonViewBean.MyJacksonView1;
|
||||
import static org.springframework.http.codec.json.JacksonViewBean.MyJacksonView3;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link Jackson2JsonDecoder}.
|
||||
@@ -199,6 +208,16 @@ public class Jackson2JsonDecoderTests extends AbstractDecoderTestCase<Jackson2Js
|
||||
);
|
||||
}
|
||||
|
||||
@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) {
|
||||
return Mono.defer(() -> {
|
||||
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
@@ -26,7 +26,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;
|
||||
@@ -248,7 +247,8 @@ public class Jackson2TokenizerTests extends AbstractLeakCheckingTestCase {
|
||||
public void errorInStream() {
|
||||
DataBuffer buffer = stringBuffer("{\"id\":1,\"name\":");
|
||||
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)
|
||||
.expectError(RuntimeException.class)
|
||||
@@ -258,7 +258,8 @@ public class Jackson2TokenizerTests extends AbstractLeakCheckingTestCase {
|
||||
@Test // SPR-16521
|
||||
public void jsonEOFExceptionIsWrappedAsDecodingError() {
|
||||
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)
|
||||
.expectError(DecodingException.class)
|
||||
@@ -268,11 +269,10 @@ public class Jackson2TokenizerTests extends AbstractLeakCheckingTestCase {
|
||||
@Test
|
||||
public void useBigDecimalForFloats() {
|
||||
for (boolean useBigDecimalForFloats : Arrays.asList(false, true)) {
|
||||
this.objectMapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, useBigDecimalForFloats);
|
||||
|
||||
Flux<DataBuffer> source = Flux.just(stringBuffer("1E+2"));
|
||||
Flux<TokenBuffer> tokens =
|
||||
Jackson2Tokenizer.tokenize(source, this.jsonFactory, this.objectMapper, false, -1);
|
||||
Jackson2Tokenizer.tokenize(source, this.jsonFactory, this.objectMapper, false,
|
||||
useBigDecimalForFloats, -1);
|
||||
|
||||
StepVerifier.create(tokens)
|
||||
.assertNext(tokenBuffer -> {
|
||||
@@ -300,7 +300,7 @@ public class Jackson2TokenizerTests extends AbstractLeakCheckingTestCase {
|
||||
|
||||
Flux<TokenBuffer> 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 -> {
|
||||
|
||||
Reference in New Issue
Block a user