From da0754e884622d1812da66f88255451b7347cabc Mon Sep 17 00:00:00 2001 From: Sebastien Deleuze Date: Mon, 8 Apr 2019 14:37:37 +0200 Subject: [PATCH] Fix a regression in Jackson builder module registration This commit brings back the support for registration of multiple Jackson modules with a null typeId. Closes gh-22763 --- .../json/Jackson2ObjectMapperBuilder.java | 37 +++++++++++----- .../Jackson2ObjectMapperBuilderTests.java | 44 +++++++++++++++++++ 2 files changed, 70 insertions(+), 11 deletions(-) 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 14002f1afbe..05e569b14d2 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 @@ -18,6 +18,7 @@ package org.springframework.http.converter.json; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; @@ -61,6 +62,8 @@ import org.springframework.core.KotlinDetector; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.util.xml.StaxUtils; @@ -617,24 +620,27 @@ 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) { - ObjectMapper.findModules(this.moduleClassLoader).forEach(module -> modulesToRegister.put(module.getTypeId(), module)); + ObjectMapper.findModules(this.moduleClassLoader).forEach(module -> registerModule(module, modulesToRegister)); } else if (this.findWellKnownModules) { registerWellKnownModulesIfAvailable(modulesToRegister); } if (this.modules != null) { - this.modules.forEach(module -> modulesToRegister.put(module.getTypeId(), module)); + this.modules.forEach(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); } } - objectMapper.registerModules(modulesToRegister.values()); + List modules = new ArrayList<>(); + for (List nestedModules : modulesToRegister.values()) { + modules.addAll(nestedModules); + } + objectMapper.registerModules(modules); if (this.dateFormat != null) { objectMapper.setDateFormat(this.dateFormat); @@ -684,6 +690,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 @@ -730,12 +745,12 @@ public class Jackson2ObjectMapperBuilder { } @SuppressWarnings("unchecked") - private void registerWellKnownModulesIfAvailable(Map modulesToRegister) { + private void registerWellKnownModulesIfAvailable(MultiValueMap modulesToRegister) { try { 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 @@ -745,7 +760,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 @@ -757,7 +772,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 @@ -770,7 +785,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) { if (!kotlinWarningLogged) { 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 edbd8727694..1639842363f 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; @@ -325,6 +326,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(); @@ -651,4 +670,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; + } + } + }