diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java b/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java index c4a3f72d37fbceb84da0299e4b9f90677a9aa0b2..b0b0286303c68b6347c57be00b3ee1000593cc8d 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java @@ -56,6 +56,8 @@ import org.springframework.beans.FatalBeanException; import org.springframework.context.ApplicationContext; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.util.StreamUtils; import org.springframework.util.StringUtils; @@ -599,11 +601,10 @@ public class Jackson2ObjectMapperBuilder { public void configure(ObjectMapper objectMapper) { Assert.notNull(objectMapper, "ObjectMapper must not be null"); - Map modulesToRegister = new LinkedHashMap(); + MultiValueMap modulesToRegister = new LinkedMultiValueMap(); if (this.findModulesViaServiceLoader) { - // Jackson 2.2+ for (Module module : ObjectMapper.findModules(this.moduleClassLoader)) { - modulesToRegister.put(module.getTypeId(), module); + registerModule(module, modulesToRegister); } } else if (this.findWellKnownModules) { @@ -612,18 +613,19 @@ public class Jackson2ObjectMapperBuilder { if (this.modules != null) { for (Module module : this.modules) { - modulesToRegister.put(module.getTypeId(), module); + registerModule(module, modulesToRegister); } } if (this.moduleClasses != null) { for (Class moduleClass : this.moduleClasses) { - Module module = BeanUtils.instantiateClass(moduleClass); - modulesToRegister.put(module.getTypeId(), module); + registerModule(BeanUtils.instantiateClass(moduleClass), modulesToRegister); } } // Using Jackson 2.0+ registerModule method, not Jackson 2.2+ registerModules - for (Module module : modulesToRegister.values()) { - objectMapper.registerModule(module); + for (List nestedModules : modulesToRegister.values()) { + for (Module module : nestedModules) { + objectMapper.registerModule(module); + } } if (this.dateFormat != null) { @@ -678,6 +680,15 @@ public class Jackson2ObjectMapperBuilder { } } + private void registerModule(Module module, MultiValueMap modulesToRegister) { + if (module.getTypeId() == null) { + modulesToRegister.add(SimpleModule.class.getName(), module); + } + else { + modulesToRegister.set(module.getTypeId(), module); + } + } + // Any change to this method should be also applied to spring-jms and spring-messaging // MappingJackson2MessageConverter default constructors @@ -726,14 +737,14 @@ public class Jackson2ObjectMapperBuilder { } @SuppressWarnings("unchecked") - private void registerWellKnownModulesIfAvailable(Map modulesToRegister) { + private void registerWellKnownModulesIfAvailable(MultiValueMap modulesToRegister) { // Java 7 java.nio.file.Path class present? if (ClassUtils.isPresent("java.nio.file.Path", this.moduleClassLoader)) { try { Class jdk7ModuleClass = (Class) ClassUtils.forName("com.fasterxml.jackson.datatype.jdk7.Jdk7Module", this.moduleClassLoader); Module jdk7Module = BeanUtils.instantiateClass(jdk7ModuleClass); - modulesToRegister.put(jdk7Module.getTypeId(), jdk7Module); + modulesToRegister.set(jdk7Module.getTypeId(), jdk7Module); } catch (ClassNotFoundException ex) { // jackson-datatype-jdk7 not available @@ -746,7 +757,7 @@ public class Jackson2ObjectMapperBuilder { Class jdk8ModuleClass = (Class) ClassUtils.forName("com.fasterxml.jackson.datatype.jdk8.Jdk8Module", this.moduleClassLoader); Module jdk8Module = BeanUtils.instantiateClass(jdk8ModuleClass); - modulesToRegister.put(jdk8Module.getTypeId(), jdk8Module); + modulesToRegister.set(jdk8Module.getTypeId(), jdk8Module); } catch (ClassNotFoundException ex) { // jackson-datatype-jdk8 not available @@ -759,7 +770,7 @@ public class Jackson2ObjectMapperBuilder { Class javaTimeModuleClass = (Class) ClassUtils.forName("com.fasterxml.jackson.datatype.jsr310.JavaTimeModule", this.moduleClassLoader); Module javaTimeModule = BeanUtils.instantiateClass(javaTimeModuleClass); - modulesToRegister.put(javaTimeModule.getTypeId(), javaTimeModule); + modulesToRegister.set(javaTimeModule.getTypeId(), javaTimeModule); } catch (ClassNotFoundException ex) { // jackson-datatype-jsr310 not available @@ -772,7 +783,7 @@ public class Jackson2ObjectMapperBuilder { Class jodaModuleClass = (Class) ClassUtils.forName("com.fasterxml.jackson.datatype.joda.JodaModule", this.moduleClassLoader); Module jodaModule = BeanUtils.instantiateClass(jodaModuleClass); - modulesToRegister.put(jodaModule.getTypeId(), jodaModule); + modulesToRegister.set(jodaModule.getTypeId(), jodaModule); } catch (ClassNotFoundException ex) { // jackson-datatype-joda not available @@ -785,7 +796,7 @@ public class Jackson2ObjectMapperBuilder { Class kotlinModuleClass = (Class) ClassUtils.forName("com.fasterxml.jackson.module.kotlin.KotlinModule", this.moduleClassLoader); Module kotlinModule = BeanUtils.instantiateClass(kotlinModuleClass); - modulesToRegister.put(kotlinModule.getTypeId(), kotlinModule); + modulesToRegister.set(kotlinModule.getTypeId(), kotlinModule); } catch (ClassNotFoundException ex) { // jackson-module-kotlin not available diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilderTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilderTests.java index 7708ce6ada6945240182f7686a4afc4ddb5362a6..5f3e2aeaf093ada93744c1cd7c739c9da6dbdbdc 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilderTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilderTests.java @@ -33,6 +33,7 @@ import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.TimeZone; +import java.util.stream.StreamSupport; import com.fasterxml.jackson.annotation.JsonFilter; import com.fasterxml.jackson.annotation.JsonInclude; @@ -309,6 +310,24 @@ public class Jackson2ObjectMapperBuilderTests { assertNotNull(demoPojo.getOffsetDateTime()); } + @Test // gh-22740 + public void registerMultipleModulesWithNullTypeId() { + Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder(); + SimpleModule fooModule = new SimpleModule(); + fooModule.addSerializer(new FooSerializer()); + SimpleModule barModule = new SimpleModule(); + barModule.addSerializer(new BarSerializer()); + builder.modulesToInstall(fooModule, barModule); + ObjectMapper objectMapper = builder.build(); + assertEquals(1, StreamSupport + .stream(getSerializerFactoryConfig(objectMapper).serializers().spliterator(), false) + .filter(s -> s.findSerializer(null, SimpleType.construct(Foo.class), null) != null) + .count()); + assertEquals(1, StreamSupport + .stream(getSerializerFactoryConfig(objectMapper).serializers().spliterator(), false) + .filter(s -> s.findSerializer(null, SimpleType.construct(Bar.class), null) != null) + .count()); + } private static SerializerFactoryConfig getSerializerFactoryConfig(ObjectMapper objectMapper) { return ((BasicSerializerFactory) objectMapper.getSerializerFactory()).getFactoryConfig(); @@ -606,4 +625,29 @@ public class Jackson2ObjectMapperBuilderTests { } + static class Foo {} + + static class Bar {} + + static class FooSerializer extends JsonSerializer { + @Override + public void serialize(Foo value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + } + + @Override + public Class handledType() { + return Foo.class; + } + } + + static class BarSerializer extends JsonSerializer { + @Override + public void serialize(Bar value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + } + @Override + public Class handledType() { + return Bar.class; + } + } + }