Skip to content

Commit 3f96d6e

Browse files
committed
Fix NullPointerException resolving type hints for JSON with duplicate properties.
GenericJacksonJsonRedisSerializer.TypeResolver.readTree(...) built the JSON node tree using a DeserializationContext from ObjectMapper._deserializationContext() that was never bound to the parser, so its stream-read capabilities stayed uninitialized. Deserializing any value whose JSON contains a duplicate property then failed with a NullPointerException in BaseNodeDeserializer._handleDuplicateProperty, which consults StreamReadCapability.DUPLICATE_PROPERTIES. Duplicate property names are valid JSON (RFC 8259) and occur in practice, for example in values written by the Jackson 2 serializer. Bind the parser to the context (as ObjectMapper._readTreeAndClose does via assignAndReturnParser) so the type-hint tree scan handles them. Closes #3396 Signed-off-by: Tomaz Cerar <tomaz@cerar.net>
1 parent 3e5d97a commit 3f96d6e

2 files changed

Lines changed: 23 additions & 5 deletions

File tree

src/main/java/org/springframework/data/redis/serializer/GenericJacksonJsonRedisSerializer.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@
2323
import tools.jackson.core.Version;
2424
import tools.jackson.databind.DefaultTyping;
2525
import tools.jackson.databind.DeserializationConfig;
26-
import tools.jackson.databind.DeserializationContext;
2726
import tools.jackson.databind.DeserializationFeature;
2827
import tools.jackson.databind.JacksonModule;
2928
import tools.jackson.databind.JavaType;
3029
import tools.jackson.databind.JsonNode;
3130
import tools.jackson.databind.ObjectMapper;
3231
import tools.jackson.databind.SerializationContext;
3332
import tools.jackson.databind.cfg.MapperBuilder;
33+
import tools.jackson.databind.deser.DeserializationContextExt;
3434
import tools.jackson.databind.deser.jackson.BaseNodeDeserializer;
3535
import tools.jackson.databind.deser.jackson.JsonNodeDeserializer;
3636
import tools.jackson.databind.json.JsonMapper;
@@ -68,6 +68,7 @@
6868
* {@link JacksonObjectWriter}.
6969
*
7070
* @author Christoph Strobl
71+
* @author Tomaz Cerar
7172
* @see JacksonObjectReader
7273
* @see JacksonObjectWriter
7374
* @see ObjectMapper
@@ -503,10 +504,12 @@ private JsonNode readTree(byte[] source) throws IOException {
503504
}
504505
}
505506

506-
/*
507-
* Hokey pokey! Oh my.
508-
*/
509-
DeserializationContext ctxt = mapper._deserializationContext();
507+
// Bind the parser to the context so its stream-read capabilities are initialized: the node
508+
// deserializer consults them (e.g. StreamReadCapability.DUPLICATE_PROPERTIES when a duplicate
509+
// field name is encountered) and would otherwise throw a NullPointerException. This mirrors the
510+
// way ObjectMapper._readTreeAndClose(...) binds the parser via assignAndReturnParser(...).
511+
DeserializationContextExt ctxt = mapper._deserializationContext();
512+
ctxt.assignParser(parser);
510513

511514
if (t == JsonToken.VALUE_NULL) {
512515
return cfg.getNodeFactory().nullNode();

src/test/java/org/springframework/data/redis/serializer/GenericJacksonJsonRedisSerializerUnitTests.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import static org.assertj.core.api.Assertions.assertThat;
1919
import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;
20+
import static org.assertj.core.api.InstanceOfAssertFactories.map;
2021
import static org.mockito.ArgumentMatchers.*;
2122
import static org.mockito.Mockito.*;
2223
import static org.springframework.test.util.ReflectionTestUtils.*;
@@ -60,6 +61,7 @@
6061
* @author Christoph Strobl
6162
* @author Mark Paluch
6263
* @author John Blum
64+
* @author Tomaz Cerar
6365
*/
6466
class GenericJacksonJsonRedisSerializerUnitTests {
6567

@@ -178,6 +180,19 @@ void shouldDeserializePrimitiveArrayWithoutTypeHint() {
178180
assertThat(result.getAvailable()).containsExactly(0, 1);
179181
}
180182

183+
@Test // GH-3396
184+
void deserializeShouldHandleDuplicatePropertyWithDefaultTyping() {
185+
186+
// Duplicate property names are valid JSON (RFC 8259) and can appear in cached values written by an
187+
// earlier serializer (e.g. Jackson 2). Resolving the type hint scans the JSON tree, which must not
188+
// fail on a duplicate field (previously a NullPointerException from an unbound DeserializationContext).
189+
byte[] source = "{\"@class\":\"java.util.LinkedHashMap\",\"a\":1,\"a\":2}".getBytes(StandardCharsets.UTF_8);
190+
191+
Object result = serializer.deserialize(source);
192+
193+
assertThat(result).asInstanceOf(map(String.class, Object.class)).containsEntry("a", 2);
194+
}
195+
181196
@Test // GH-2322
182197
void readsToMapForNonDefaultTyping() {
183198

0 commit comments

Comments
 (0)