From a089027e7dc3a3f480379da3ef38cf6ebee84abd Mon Sep 17 00:00:00 2001 From: Sebastien Deleuze Date: Mon, 8 Apr 2019 15:37:10 +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-22740 --- .../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 dbbfefbe828..433fef384dc 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.LinkedHashMap; import java.util.LinkedList; @@ -62,6 +63,8 @@ import org.springframework.http.HttpLogging; 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; @@ -632,24 +635,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); @@ -701,6 +707,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 @@ -747,12 +762,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 @@ -762,7 +777,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 @@ -774,7 +789,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 @@ -787,7 +802,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 ec38333a600..5c8756871d7 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.JsonAutoDetect.Visibility; import com.fasterxml.jackson.annotation.JsonFilter; @@ -329,6 +330,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(); @@ -679,4 +698,29 @@ public class Jackson2ObjectMapperBuilderTests { public static class MyXmlFactory extends XmlFactory { } + 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; + } + } + }