Skip to content

Commit f6693e7

Browse files
committed
Jackson2Tokenizer passes DeserializationContext into TokenBuffer
Closes gh-22510
1 parent 6c59920 commit f6693e7

File tree

3 files changed

+51
-48
lines changed

3 files changed

+51
-48
lines changed

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,17 @@ public boolean canDecode(ResolvableType elementType, @Nullable MimeType mimeType
8686
public Flux<Object> decode(Publisher<DataBuffer> input, ResolvableType elementType,
8787
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
8888

89-
Flux<TokenBuffer> tokens = Jackson2Tokenizer.tokenize(Flux.from(input), this.jsonFactory, true);
89+
Flux<TokenBuffer> tokens = Jackson2Tokenizer.tokenize(
90+
Flux.from(input), this.jsonFactory, getObjectMapper().getDeserializationContext(), true);
9091
return decodeInternal(tokens, elementType, mimeType, hints);
9192
}
9293

9394
@Override
9495
public Mono<Object> decodeToMono(Publisher<DataBuffer> input, ResolvableType elementType,
9596
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
9697

97-
Flux<TokenBuffer> tokens = Jackson2Tokenizer.tokenize(Flux.from(input), this.jsonFactory, false);
98+
Flux<TokenBuffer> tokens = Jackson2Tokenizer.tokenize(
99+
Flux.from(input), this.jsonFactory, getObjectMapper().getDeserializationContext(), false);
98100
return decodeInternal(tokens, elementType, mimeType, hints).singleOrEmpty();
99101
}
100102

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

+34-36
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 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.
@@ -26,20 +26,22 @@
2626
import com.fasterxml.jackson.core.JsonProcessingException;
2727
import com.fasterxml.jackson.core.JsonToken;
2828
import com.fasterxml.jackson.core.async.ByteArrayFeeder;
29+
import com.fasterxml.jackson.databind.DeserializationContext;
2930
import com.fasterxml.jackson.databind.util.TokenBuffer;
3031
import reactor.core.publisher.Flux;
3132

3233
import org.springframework.core.codec.DecodingException;
3334
import org.springframework.core.io.buffer.DataBuffer;
3435
import org.springframework.core.io.buffer.DataBufferUtils;
35-
import org.springframework.util.Assert;
3636

3737
/**
3838
* {@link Function} to transform a JSON stream of arbitrary size, byte array
3939
* chunks into a {@code Flux<TokenBuffer>} where each token buffer is a
4040
* well-formed JSON object.
4141
*
4242
* @author Arjen Poutsma
43+
* @author Rossen Stoyanchev
44+
* @author Juergen Hoeller
4345
* @since 5.0
4446
*/
4547
final class Jackson2Tokenizer {
@@ -59,36 +61,15 @@ final class Jackson2Tokenizer {
5961
private final ByteArrayFeeder inputFeeder;
6062

6163

62-
private Jackson2Tokenizer(JsonParser parser, boolean tokenizeArrayElements) {
63-
Assert.notNull(parser, "'parser' must not be null");
64+
private Jackson2Tokenizer(
65+
JsonParser parser, DeserializationContext deserializationContext, boolean tokenizeArrayElements) {
6466

6567
this.parser = parser;
6668
this.tokenizeArrayElements = tokenizeArrayElements;
67-
this.tokenBuffer = new TokenBuffer(parser);
69+
this.tokenBuffer = new TokenBuffer(parser, deserializationContext);
6870
this.inputFeeder = (ByteArrayFeeder) this.parser.getNonBlockingInputFeeder();
6971
}
7072

71-
/**
72-
* Tokenize the given {@code Flux<DataBuffer>} into {@code Flux<TokenBuffer>}.
73-
* @param dataBuffers the source data buffers
74-
* @param jsonFactory the factory to use
75-
* @param tokenizeArrayElements if {@code true} and the "top level" JSON
76-
* object is an array, each element is returned individually, immediately
77-
* after it is received.
78-
* @return the result token buffers
79-
*/
80-
public static Flux<TokenBuffer> tokenize(Flux<DataBuffer> dataBuffers, JsonFactory jsonFactory,
81-
boolean tokenizeArrayElements) {
82-
83-
try {
84-
JsonParser parser = jsonFactory.createNonBlockingByteArrayParser();
85-
Jackson2Tokenizer tokenizer = new Jackson2Tokenizer(parser, tokenizeArrayElements);
86-
return dataBuffers.flatMap(tokenizer::tokenize, Flux::error, tokenizer::endOfInput);
87-
}
88-
catch (IOException ex) {
89-
return Flux.error(ex);
90-
}
91-
}
9273

9374
private Flux<TokenBuffer> tokenize(DataBuffer dataBuffer) {
9475
byte[] bytes = new byte[dataBuffer.readableByteCount()];
@@ -100,8 +81,7 @@ private Flux<TokenBuffer> tokenize(DataBuffer dataBuffer) {
10081
return parseTokenBufferFlux();
10182
}
10283
catch (JsonProcessingException ex) {
103-
return Flux.error(new DecodingException(
104-
"JSON decoding error: " + ex.getOriginalMessage(), ex));
84+
return Flux.error(new DecodingException("JSON decoding error: " + ex.getOriginalMessage(), ex));
10585
}
10686
catch (IOException ex) {
10787
return Flux.error(ex);
@@ -114,8 +94,7 @@ private Flux<TokenBuffer> endOfInput() {
11494
return parseTokenBufferFlux();
11595
}
11696
catch (JsonProcessingException ex) {
117-
return Flux.error(new DecodingException(
118-
"JSON decoding error: " + ex.getOriginalMessage(), ex));
97+
return Flux.error(new DecodingException("JSON decoding error: " + ex.getOriginalMessage(), ex));
11998
}
12099
catch (IOException ex) {
121100
return Flux.error(ex);
@@ -128,12 +107,11 @@ private Flux<TokenBuffer> parseTokenBufferFlux() throws IOException {
128107
while (true) {
129108
JsonToken token = this.parser.nextToken();
130109
// SPR-16151: Smile data format uses null to separate documents
131-
if ((token == JsonToken.NOT_AVAILABLE) ||
110+
if (token == JsonToken.NOT_AVAILABLE ||
132111
(token == null && (token = this.parser.nextToken()) == null)) {
133112
break;
134113
}
135114
updateDepth(token);
136-
137115
if (!this.tokenizeArrayElements) {
138116
processTokenNormal(token, result);
139117
}
@@ -164,8 +142,7 @@ private void updateDepth(JsonToken token) {
164142
private void processTokenNormal(JsonToken token, List<TokenBuffer> result) throws IOException {
165143
this.tokenBuffer.copyCurrentEvent(this.parser);
166144

167-
if ((token.isStructEnd() || token.isScalarValue()) &&
168-
this.objectDepth == 0 && this.arrayDepth == 0) {
145+
if ((token.isStructEnd() || token.isScalarValue()) && this.objectDepth == 0 && this.arrayDepth == 0) {
169146
result.add(this.tokenBuffer);
170147
this.tokenBuffer = new TokenBuffer(this.parser);
171148
}
@@ -177,8 +154,7 @@ private void processTokenArray(JsonToken token, List<TokenBuffer> result) throws
177154
this.tokenBuffer.copyCurrentEvent(this.parser);
178155
}
179156

180-
if (this.objectDepth == 0 &&
181-
(this.arrayDepth == 0 || this.arrayDepth == 1) &&
157+
if (this.objectDepth == 0 && (this.arrayDepth == 0 || this.arrayDepth == 1) &&
182158
(token == JsonToken.END_OBJECT || token.isScalarValue())) {
183159
result.add(this.tokenBuffer);
184160
this.tokenBuffer = new TokenBuffer(this.parser);
@@ -190,4 +166,26 @@ private boolean isTopLevelArrayToken(JsonToken token) {
190166
(token == JsonToken.END_ARRAY && this.arrayDepth == 0));
191167
}
192168

169+
170+
/**
171+
* Tokenize the given {@code Flux<DataBuffer>} into {@code Flux<TokenBuffer>}.
172+
* @param dataBuffers the source data buffers
173+
* @param jsonFactory the factory to use
174+
* @param tokenizeArrayElements if {@code true} and the "top level" JSON object is
175+
* an array, each element is returned individually immediately after it is received
176+
* @return the resulting token buffers
177+
*/
178+
public static Flux<TokenBuffer> tokenize(Flux<DataBuffer> dataBuffers, JsonFactory jsonFactory,
179+
DeserializationContext deserializationContext, boolean tokenizeArrayElements) {
180+
181+
try {
182+
JsonParser parser = jsonFactory.createNonBlockingByteArrayParser();
183+
Jackson2Tokenizer tokenizer = new Jackson2Tokenizer(parser, deserializationContext, tokenizeArrayElements);
184+
return dataBuffers.flatMap(tokenizer::tokenize, Flux::error, tokenizer::endOfInput);
185+
}
186+
catch (IOException ex) {
187+
return Flux.error(ex);
188+
}
189+
}
190+
193191
}

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

+13-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 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.
@@ -37,26 +37,28 @@
3737
import org.springframework.core.io.buffer.AbstractLeakCheckingTestCase;
3838
import org.springframework.core.io.buffer.DataBuffer;
3939

40-
import static java.util.Arrays.asList;
41-
import static java.util.Collections.singletonList;
40+
import static java.util.Arrays.*;
41+
import static java.util.Collections.*;
4242

4343
/**
4444
* @author Arjen Poutsma
4545
* @author Rossen Stoyanchev
46+
* @author Juergen Hoeller
4647
*/
4748
public class Jackson2TokenizerTests extends AbstractLeakCheckingTestCase {
4849

49-
private ObjectMapper objectMapper;
50-
5150
private JsonFactory jsonFactory;
5251

52+
private ObjectMapper objectMapper;
53+
5354

5455
@Before
5556
public void createParser() {
5657
this.jsonFactory = new JsonFactory();
5758
this.objectMapper = new ObjectMapper(this.jsonFactory);
5859
}
5960

61+
6062
@Test
6163
public void doNotTokenizeArrayElements() {
6264
testTokenize(
@@ -185,7 +187,8 @@ public void errorInStream() {
185187
Flux<DataBuffer> source = Flux.just(buffer)
186188
.concatWith(Flux.error(new RuntimeException()));
187189

188-
Flux<TokenBuffer> result = Jackson2Tokenizer.tokenize(source, this.jsonFactory, true);
190+
Flux<TokenBuffer> result = Jackson2Tokenizer.tokenize(
191+
source, this.jsonFactory, this.objectMapper.getDeserializationContext(), true);
189192

190193
StepVerifier.create(result)
191194
.expectError(RuntimeException.class)
@@ -195,7 +198,8 @@ public void errorInStream() {
195198
@Test // SPR-16521
196199
public void jsonEOFExceptionIsWrappedAsDecodingError() {
197200
Flux<DataBuffer> source = Flux.just(stringBuffer("{\"status\": \"noClosingQuote}"));
198-
Flux<TokenBuffer> tokens = Jackson2Tokenizer.tokenize(source, this.jsonFactory, false);
201+
Flux<TokenBuffer> tokens = Jackson2Tokenizer.tokenize(
202+
source, this.jsonFactory, this.objectMapper.getDeserializationContext(), false);
199203

200204
StepVerifier.create(tokens)
201205
.expectError(DecodingException.class)
@@ -204,10 +208,9 @@ public void jsonEOFExceptionIsWrappedAsDecodingError() {
204208

205209

206210
private void testTokenize(List<String> source, List<String> expected, boolean tokenizeArrayElements) {
207-
208211
Flux<TokenBuffer> tokenBufferFlux = Jackson2Tokenizer.tokenize(
209212
Flux.fromIterable(source).map(this::stringBuffer),
210-
this.jsonFactory,
213+
this.jsonFactory, this.objectMapper.getDeserializationContext(),
211214
tokenizeArrayElements);
212215

213216
Flux<String> result = tokenBufferFlux
@@ -234,7 +237,6 @@ private DataBuffer stringBuffer(String value) {
234237
}
235238

236239

237-
238240
private static class JSONAssertConsumer implements Consumer<String> {
239241

240242
private final String expected;
@@ -253,4 +255,5 @@ public void accept(String s) {
253255
}
254256
}
255257
}
258+
256259
}

0 commit comments

Comments
 (0)