Skip to content

Commit 0d7494a

Browse files
committed
Honour ObjectMapper feature in Jackson2Tokenizer
After this commit, Jackson2Tokenizer honours ObjectMapper's DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS feature when creating TokenBuffers. Closes gh-24479
1 parent a59a338 commit 0d7494a

File tree

2 files changed

+65
-12
lines changed

2 files changed

+65
-12
lines changed

spring-web/src/main/java/org/springframework/http/codec/json/Jackson2Tokenizer.java

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@
2727
import com.fasterxml.jackson.core.JsonToken;
2828
import com.fasterxml.jackson.core.async.ByteArrayFeeder;
2929
import com.fasterxml.jackson.databind.DeserializationContext;
30+
import com.fasterxml.jackson.databind.DeserializationFeature;
3031
import com.fasterxml.jackson.databind.ObjectMapper;
3132
import com.fasterxml.jackson.databind.deser.DefaultDeserializationContext;
3233
import com.fasterxml.jackson.databind.util.TokenBuffer;
@@ -36,6 +37,7 @@
3637
import org.springframework.core.io.buffer.DataBuffer;
3738
import org.springframework.core.io.buffer.DataBufferLimitException;
3839
import org.springframework.core.io.buffer.DataBufferUtils;
40+
import org.springframework.lang.Nullable;
3941

4042
/**
4143
* {@link Function} to transform a JSON stream of arbitrary size, byte array
@@ -55,34 +57,39 @@ final class Jackson2Tokenizer {
5557

5658
private final boolean tokenizeArrayElements;
5759

58-
private TokenBuffer tokenBuffer;
60+
private final boolean forceUseOfBigDecimal;
61+
62+
private final int maxInMemorySize;
5963

6064
private int objectDepth;
6165

6266
private int arrayDepth;
6367

64-
private final int maxInMemorySize;
65-
6668
private int byteCount;
6769

70+
@Nullable // yet initialized by calling createToken() in the constructor
71+
private TokenBuffer tokenBuffer;
72+
6873

6974
// TODO: change to ByteBufferFeeder when supported by Jackson
7075
// See https://github.com/FasterXML/jackson-core/issues/478
7176
private final ByteArrayFeeder inputFeeder;
7277

7378

7479
private Jackson2Tokenizer(JsonParser parser, DeserializationContext deserializationContext,
75-
boolean tokenizeArrayElements, int maxInMemorySize) {
80+
boolean tokenizeArrayElements, boolean forceUseOfBigDecimal, int maxInMemorySize) {
7681

7782
this.parser = parser;
7883
this.deserializationContext = deserializationContext;
7984
this.tokenizeArrayElements = tokenizeArrayElements;
80-
this.tokenBuffer = new TokenBuffer(parser, deserializationContext);
85+
this.forceUseOfBigDecimal = forceUseOfBigDecimal;
8186
this.inputFeeder = (ByteArrayFeeder) this.parser.getNonBlockingInputFeeder();
8287
this.maxInMemorySize = maxInMemorySize;
88+
createToken();
8389
}
8490

8591

92+
8693
private Flux<TokenBuffer> tokenize(DataBuffer dataBuffer) {
8794
int bufferSize = dataBuffer.readableByteCount();
8895
byte[] bytes = new byte[dataBuffer.readableByteCount()];
@@ -132,6 +139,9 @@ else if (token == null ) { // !previousNull
132139
previousNull = true;
133140
continue;
134141
}
142+
else {
143+
previousNull = false;
144+
}
135145
updateDepth(token);
136146
if (!this.tokenizeArrayElements) {
137147
processTokenNormal(token, result);
@@ -165,7 +175,7 @@ private void processTokenNormal(JsonToken token, List<TokenBuffer> result) throw
165175

166176
if ((token.isStructEnd() || token.isScalarValue()) && this.objectDepth == 0 && this.arrayDepth == 0) {
167177
result.add(this.tokenBuffer);
168-
this.tokenBuffer = new TokenBuffer(this.parser, this.deserializationContext);
178+
createToken();
169179
}
170180

171181
}
@@ -178,10 +188,15 @@ private void processTokenArray(JsonToken token, List<TokenBuffer> result) throws
178188
if (this.objectDepth == 0 && (this.arrayDepth == 0 || this.arrayDepth == 1) &&
179189
(token == JsonToken.END_OBJECT || token.isScalarValue())) {
180190
result.add(this.tokenBuffer);
181-
this.tokenBuffer = new TokenBuffer(this.parser, this.deserializationContext);
191+
createToken();
182192
}
183193
}
184194

195+
private void createToken() {
196+
this.tokenBuffer = new TokenBuffer(this.parser, this.deserializationContext);
197+
this.tokenBuffer.forceUseOfBigDecimal(this.forceUseOfBigDecimal);
198+
}
199+
185200
private boolean isTopLevelArrayToken(JsonToken token) {
186201
return this.objectDepth == 0 && ((token == JsonToken.START_ARRAY && this.arrayDepth == 1) ||
187202
(token == JsonToken.END_ARRAY && this.arrayDepth == 0));
@@ -229,7 +244,9 @@ public static Flux<TokenBuffer> tokenize(Flux<DataBuffer> dataBuffers, JsonFacto
229244
context = ((DefaultDeserializationContext) context).createInstance(
230245
objectMapper.getDeserializationConfig(), parser, objectMapper.getInjectableValues());
231246
}
232-
Jackson2Tokenizer tokenizer = new Jackson2Tokenizer(parser, context, tokenizeArrays, maxInMemorySize);
247+
boolean forceUseOfBigDecimal = objectMapper.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
248+
Jackson2Tokenizer tokenizer = new Jackson2Tokenizer(parser, context, tokenizeArrays, forceUseOfBigDecimal,
249+
maxInMemorySize);
233250
return dataBuffers.flatMap(tokenizer::tokenize, Flux::error, tokenizer::endOfInput);
234251
}
235252
catch (IOException ex) {

spring-web/src/test/java/org/springframework/http/codec/json/Jackson2TokenizerTests.java

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,10 +19,14 @@
1919
import java.io.IOException;
2020
import java.io.UncheckedIOException;
2121
import java.nio.charset.StandardCharsets;
22+
import java.util.Arrays;
2223
import java.util.List;
2324

2425
import com.fasterxml.jackson.core.JsonFactory;
26+
import com.fasterxml.jackson.core.JsonParser;
27+
import com.fasterxml.jackson.core.JsonToken;
2528
import com.fasterxml.jackson.core.TreeNode;
29+
import com.fasterxml.jackson.databind.DeserializationFeature;
2630
import com.fasterxml.jackson.databind.ObjectMapper;
2731
import com.fasterxml.jackson.databind.util.TokenBuffer;
2832
import org.json.JSONException;
@@ -37,8 +41,10 @@
3741
import org.springframework.core.io.buffer.DataBuffer;
3842
import org.springframework.core.io.buffer.DataBufferLimitException;
3943

40-
import static java.util.Arrays.*;
41-
import static java.util.Collections.*;
44+
import static java.util.Arrays.asList;
45+
import static java.util.Collections.singletonList;
46+
import static org.junit.Assert.assertEquals;
47+
import static org.junit.Assert.fail;
4248

4349
/**
4450
* @author Arjen Poutsma
@@ -259,6 +265,36 @@ public void jsonEOFExceptionIsWrappedAsDecodingError() {
259265
.verify();
260266
}
261267

268+
@Test
269+
public void useBigDecimalForFloats() {
270+
for (boolean useBigDecimalForFloats : Arrays.asList(false, true)) {
271+
this.objectMapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, useBigDecimalForFloats);
272+
273+
Flux<DataBuffer> source = Flux.just(stringBuffer("1E+2"));
274+
Flux<TokenBuffer> tokens =
275+
Jackson2Tokenizer.tokenize(source, this.jsonFactory, this.objectMapper, false, -1);
276+
277+
StepVerifier.create(tokens)
278+
.assertNext(tokenBuffer -> {
279+
try {
280+
JsonParser parser = tokenBuffer.asParser();
281+
JsonToken token = parser.nextToken();
282+
assertEquals(JsonToken.VALUE_NUMBER_FLOAT, token);
283+
JsonParser.NumberType numberType = parser.getNumberType();
284+
if (useBigDecimalForFloats) {
285+
assertEquals(JsonParser.NumberType.BIG_DECIMAL, numberType);
286+
}
287+
else {
288+
assertEquals(JsonParser.NumberType.DOUBLE, numberType);
289+
}
290+
}
291+
catch (IOException ex) {
292+
fail(ex.getMessage());
293+
}
294+
})
295+
.verifyComplete();
296+
}
297+
}
262298

263299
private Flux<String> decode(List<String> source, boolean tokenize, int maxInMemorySize) {
264300

0 commit comments

Comments
 (0)