Skip to content

Commit 0422305

Browse files
committed
Fix Jackson builder modulesToInstall override behavior
This commit updates Jackson2ObjectMapperBuilder in order to ensure that modules specified via modulesToInstall eventually override the default ones. Closes gh-22624
1 parent 7e61826 commit 0422305

File tree

3 files changed

+98
-19
lines changed

3 files changed

+98
-19
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,7 @@ project("spring-web") {
727727
testCompile("com.fasterxml.jackson.datatype:jackson-datatype-joda:2.8.11")
728728
testCompile("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.8.11")
729729
testCompile("com.fasterxml.jackson.module:jackson-module-kotlin:2.8.11.1")
730+
testCompile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.8.11")
730731
testCompile("com.squareup.okhttp3:mockwebserver:${okhttp3Version}")
731732
testRuntime("com.sun.mail:javax.mail:${javamailVersion}")
732733
}

spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 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.
@@ -599,25 +599,32 @@ public <T extends ObjectMapper> T build() {
599599
public void configure(ObjectMapper objectMapper) {
600600
Assert.notNull(objectMapper, "ObjectMapper must not be null");
601601

602+
Map<Object, Module> modulesToRegister = new LinkedHashMap<Object, Module>();
602603
if (this.findModulesViaServiceLoader) {
603604
// Jackson 2.2+
604-
objectMapper.registerModules(ObjectMapper.findModules(this.moduleClassLoader));
605+
for (Module module : ObjectMapper.findModules(this.moduleClassLoader)) {
606+
modulesToRegister.put(module.getTypeId(), module);
607+
}
605608
}
606609
else if (this.findWellKnownModules) {
607-
registerWellKnownModulesIfAvailable(objectMapper);
610+
registerWellKnownModulesIfAvailable(modulesToRegister);
608611
}
609612

610613
if (this.modules != null) {
611614
for (Module module : this.modules) {
612-
// Using Jackson 2.0+ registerModule method, not Jackson 2.2+ registerModules
613-
objectMapper.registerModule(module);
615+
modulesToRegister.put(module.getTypeId(), module);
614616
}
615617
}
616618
if (this.moduleClasses != null) {
617-
for (Class<? extends Module> module : this.moduleClasses) {
618-
objectMapper.registerModule(BeanUtils.instantiate(module));
619+
for (Class<? extends Module> moduleClass : this.moduleClasses) {
620+
Module module = BeanUtils.instantiateClass(moduleClass);
621+
modulesToRegister.put(module.getTypeId(), module);
619622
}
620623
}
624+
// Using Jackson 2.0+ registerModule method, not Jackson 2.2+ registerModules
625+
for (Module module : modulesToRegister.values()) {
626+
objectMapper.registerModule(module);
627+
}
621628

622629
if (this.dateFormat != null) {
623630
objectMapper.setDateFormat(this.dateFormat);
@@ -719,13 +726,14 @@ else if (feature instanceof MapperFeature) {
719726
}
720727

721728
@SuppressWarnings("unchecked")
722-
private void registerWellKnownModulesIfAvailable(ObjectMapper objectMapper) {
729+
private void registerWellKnownModulesIfAvailable(Map<Object, Module> modulesToRegister) {
723730
// Java 7 java.nio.file.Path class present?
724731
if (ClassUtils.isPresent("java.nio.file.Path", this.moduleClassLoader)) {
725732
try {
726-
Class<? extends Module> jdk7Module = (Class<? extends Module>)
733+
Class<? extends Module> jdk7ModuleClass = (Class<? extends Module>)
727734
ClassUtils.forName("com.fasterxml.jackson.datatype.jdk7.Jdk7Module", this.moduleClassLoader);
728-
objectMapper.registerModule(BeanUtils.instantiateClass(jdk7Module));
735+
Module jdk7Module = BeanUtils.instantiateClass(jdk7ModuleClass);
736+
modulesToRegister.put(jdk7Module.getTypeId(), jdk7Module);
729737
}
730738
catch (ClassNotFoundException ex) {
731739
// jackson-datatype-jdk7 not available
@@ -735,9 +743,10 @@ private void registerWellKnownModulesIfAvailable(ObjectMapper objectMapper) {
735743
// Java 8 java.util.Optional class present?
736744
if (ClassUtils.isPresent("java.util.Optional", this.moduleClassLoader)) {
737745
try {
738-
Class<? extends Module> jdk8Module = (Class<? extends Module>)
746+
Class<? extends Module> jdk8ModuleClass = (Class<? extends Module>)
739747
ClassUtils.forName("com.fasterxml.jackson.datatype.jdk8.Jdk8Module", this.moduleClassLoader);
740-
objectMapper.registerModule(BeanUtils.instantiateClass(jdk8Module));
748+
Module jdk8Module = BeanUtils.instantiateClass(jdk8ModuleClass);
749+
modulesToRegister.put(jdk8Module.getTypeId(), jdk8Module);
741750
}
742751
catch (ClassNotFoundException ex) {
743752
// jackson-datatype-jdk8 not available
@@ -747,9 +756,10 @@ private void registerWellKnownModulesIfAvailable(ObjectMapper objectMapper) {
747756
// Java 8 java.time package present?
748757
if (ClassUtils.isPresent("java.time.LocalDate", this.moduleClassLoader)) {
749758
try {
750-
Class<? extends Module> javaTimeModule = (Class<? extends Module>)
759+
Class<? extends Module> javaTimeModuleClass = (Class<? extends Module>)
751760
ClassUtils.forName("com.fasterxml.jackson.datatype.jsr310.JavaTimeModule", this.moduleClassLoader);
752-
objectMapper.registerModule(BeanUtils.instantiateClass(javaTimeModule));
761+
Module javaTimeModule = BeanUtils.instantiateClass(javaTimeModuleClass);
762+
modulesToRegister.put(javaTimeModule.getTypeId(), javaTimeModule);
753763
}
754764
catch (ClassNotFoundException ex) {
755765
// jackson-datatype-jsr310 not available
@@ -759,9 +769,10 @@ private void registerWellKnownModulesIfAvailable(ObjectMapper objectMapper) {
759769
// Joda-Time present?
760770
if (ClassUtils.isPresent("org.joda.time.LocalDate", this.moduleClassLoader)) {
761771
try {
762-
Class<? extends Module> jodaModule = (Class<? extends Module>)
772+
Class<? extends Module> jodaModuleClass = (Class<? extends Module>)
763773
ClassUtils.forName("com.fasterxml.jackson.datatype.joda.JodaModule", this.moduleClassLoader);
764-
objectMapper.registerModule(BeanUtils.instantiateClass(jodaModule));
774+
Module jodaModule = BeanUtils.instantiateClass(jodaModuleClass);
775+
modulesToRegister.put(jodaModule.getTypeId(), jodaModule);
765776
}
766777
catch (ClassNotFoundException ex) {
767778
// jackson-datatype-joda not available
@@ -771,9 +782,10 @@ private void registerWellKnownModulesIfAvailable(ObjectMapper objectMapper) {
771782
// Kotlin present?
772783
if (ClassUtils.isPresent("kotlin.Unit", this.moduleClassLoader)) {
773784
try {
774-
Class<? extends Module> kotlinModule = (Class<? extends Module>)
785+
Class<? extends Module> kotlinModuleClass = (Class<? extends Module>)
775786
ClassUtils.forName("com.fasterxml.jackson.module.kotlin.KotlinModule", this.moduleClassLoader);
776-
objectMapper.registerModule(BeanUtils.instantiateClass(kotlinModule));
787+
Module kotlinModule = BeanUtils.instantiateClass(kotlinModuleClass);
788+
modulesToRegister.put(kotlinModule.getTypeId(), kotlinModule);
777789
}
778790
catch (ClassNotFoundException ex) {
779791
// jackson-module-kotlin not available

spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilderTests.java

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 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.
@@ -21,6 +21,8 @@
2121
import java.nio.file.Path;
2222
import java.nio.file.Paths;
2323
import java.text.SimpleDateFormat;
24+
import java.time.OffsetDateTime;
25+
import java.time.format.DateTimeParseException;
2426
import java.util.ArrayList;
2527
import java.util.Arrays;
2628
import java.util.Collections;
@@ -38,6 +40,7 @@
3840
import com.fasterxml.jackson.core.JsonParser;
3941
import com.fasterxml.jackson.core.JsonProcessingException;
4042
import com.fasterxml.jackson.core.Version;
43+
import com.fasterxml.jackson.databind.DeserializationContext;
4144
import com.fasterxml.jackson.databind.DeserializationFeature;
4245
import com.fasterxml.jackson.databind.JsonDeserializer;
4346
import com.fasterxml.jackson.databind.JsonMappingException;
@@ -48,6 +51,7 @@
4851
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
4952
import com.fasterxml.jackson.databind.SerializationFeature;
5053
import com.fasterxml.jackson.databind.SerializerProvider;
54+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
5155
import com.fasterxml.jackson.databind.cfg.DeserializerFactoryConfig;
5256
import com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig;
5357
import com.fasterxml.jackson.databind.deser.BasicDeserializerFactory;
@@ -64,12 +68,14 @@
6468
import com.fasterxml.jackson.databind.ser.std.NumberSerializer;
6569
import com.fasterxml.jackson.databind.type.SimpleType;
6670
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
71+
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
6772
import kotlin.ranges.IntRange;
6873
import org.joda.time.DateTime;
6974
import org.joda.time.DateTimeZone;
7075
import org.junit.Test;
7176

7277
import org.springframework.beans.FatalBeanException;
78+
import org.springframework.util.StringUtils;
7379

7480
import static org.hamcrest.Matchers.*;
7581
import static org.junit.Assert.*;
@@ -84,6 +90,8 @@ public class Jackson2ObjectMapperBuilderTests {
8490

8591
private static final String DATE_FORMAT = "yyyy-MM-dd";
8692

93+
private static final String DATA = "{\"offsetDateTime\": \"2020-01-01T00:00:00\"}";
94+
8795

8896
@Test
8997
public void settersWithNullValues() {
@@ -289,6 +297,18 @@ public void customizeWellKnownModulesWithSerializer() throws JsonProcessingExcep
289297
assertThat(new String(objectMapper.writeValueAsBytes(new Integer(4)), "UTF-8"), containsString("customid"));
290298
}
291299

300+
@Test // gh-22576
301+
public void overrideWellKnownModuleWithModule() throws IOException {
302+
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
303+
JavaTimeModule javaTimeModule = new JavaTimeModule();
304+
javaTimeModule.addDeserializer(OffsetDateTime.class, new OffsetDateTimeDeserializer());
305+
builder.modulesToInstall(javaTimeModule);
306+
builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
307+
ObjectMapper objectMapper = builder.build();
308+
DemoPojo demoPojo = objectMapper.readValue(DATA, DemoPojo.class);
309+
assertNotNull(demoPojo.getOffsetDateTime());
310+
}
311+
292312

293313
private static SerializerFactoryConfig getSerializerFactoryConfig(ObjectMapper objectMapper) {
294314
return ((BasicSerializerFactory) objectMapper.getSerializerFactory()).getFactoryConfig();
@@ -540,4 +560,50 @@ public void setList(List<T> list) {
540560
}
541561
}
542562

563+
public static class JacksonVisibilityBean {
564+
565+
private String property1;
566+
567+
public String property2;
568+
569+
public String getProperty3() {
570+
return null;
571+
}
572+
573+
}
574+
575+
static class OffsetDateTimeDeserializer extends JsonDeserializer<OffsetDateTime> {
576+
577+
private static final String CURRENT_ZONE_OFFSET = OffsetDateTime.now().getOffset().toString();
578+
579+
@Override
580+
public OffsetDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
581+
final String value = jsonParser.getValueAsString();
582+
if (StringUtils.isEmpty(value)) {
583+
return null;
584+
}
585+
try {
586+
return OffsetDateTime.parse(value);
587+
588+
} catch (DateTimeParseException exception) {
589+
return OffsetDateTime.parse(value + CURRENT_ZONE_OFFSET);
590+
}
591+
}
592+
}
593+
594+
@JsonDeserialize
595+
static class DemoPojo {
596+
597+
private OffsetDateTime offsetDateTime;
598+
599+
public OffsetDateTime getOffsetDateTime() {
600+
return offsetDateTime;
601+
}
602+
603+
public void setOffsetDateTime(OffsetDateTime offsetDateTime) {
604+
this.offsetDateTime = offsetDateTime;
605+
}
606+
607+
}
608+
543609
}

0 commit comments

Comments
 (0)