Skip to content

Commit a089027

Browse files
committed
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
1 parent 43cb1af commit a089027

File tree

2 files changed

+70
-11
lines changed

2 files changed

+70
-11
lines changed

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

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.text.DateFormat;
2020
import java.text.SimpleDateFormat;
21+
import java.util.ArrayList;
2122
import java.util.Arrays;
2223
import java.util.LinkedHashMap;
2324
import java.util.LinkedList;
@@ -62,6 +63,8 @@
6263
import org.springframework.lang.Nullable;
6364
import org.springframework.util.Assert;
6465
import org.springframework.util.ClassUtils;
66+
import org.springframework.util.LinkedMultiValueMap;
67+
import org.springframework.util.MultiValueMap;
6568
import org.springframework.util.StringUtils;
6669
import org.springframework.util.xml.StaxUtils;
6770

@@ -632,24 +635,27 @@ public <T extends ObjectMapper> T build() {
632635
public void configure(ObjectMapper objectMapper) {
633636
Assert.notNull(objectMapper, "ObjectMapper must not be null");
634637

635-
Map<Object, Module> modulesToRegister = new LinkedHashMap<>();
638+
MultiValueMap<Object, Module> modulesToRegister = new LinkedMultiValueMap<>();
636639
if (this.findModulesViaServiceLoader) {
637-
ObjectMapper.findModules(this.moduleClassLoader).forEach(module -> modulesToRegister.put(module.getTypeId(), module));
640+
ObjectMapper.findModules(this.moduleClassLoader).forEach(module -> registerModule(module, modulesToRegister));
638641
}
639642
else if (this.findWellKnownModules) {
640643
registerWellKnownModulesIfAvailable(modulesToRegister);
641644
}
642645

643646
if (this.modules != null) {
644-
this.modules.forEach(module -> modulesToRegister.put(module.getTypeId(), module));
647+
this.modules.forEach(module -> registerModule(module, modulesToRegister));
645648
}
646649
if (this.moduleClasses != null) {
647650
for (Class<? extends Module> moduleClass : this.moduleClasses) {
648-
Module module = BeanUtils.instantiateClass(moduleClass);
649-
modulesToRegister.put(module.getTypeId(), module);
651+
registerModule(BeanUtils.instantiateClass(moduleClass), modulesToRegister);
650652
}
651653
}
652-
objectMapper.registerModules(modulesToRegister.values());
654+
List<Module> modules = new ArrayList<>();
655+
for (List<Module> nestedModules : modulesToRegister.values()) {
656+
modules.addAll(nestedModules);
657+
}
658+
objectMapper.registerModules(modules);
653659

654660
if (this.dateFormat != null) {
655661
objectMapper.setDateFormat(this.dateFormat);
@@ -701,6 +707,15 @@ else if (this.applicationContext != null) {
701707
}
702708
}
703709

710+
private void registerModule(Module module, MultiValueMap<Object, Module> modulesToRegister) {
711+
if (module.getTypeId() == null) {
712+
modulesToRegister.add(SimpleModule.class.getName(), module);
713+
}
714+
else {
715+
modulesToRegister.set(module.getTypeId(), module);
716+
}
717+
}
718+
704719

705720
// Any change to this method should be also applied to spring-jms and spring-messaging
706721
// MappingJackson2MessageConverter default constructors
@@ -747,12 +762,12 @@ else if (feature instanceof MapperFeature) {
747762
}
748763

749764
@SuppressWarnings("unchecked")
750-
private void registerWellKnownModulesIfAvailable(Map<Object, Module> modulesToRegister) {
765+
private void registerWellKnownModulesIfAvailable(MultiValueMap<Object, Module> modulesToRegister) {
751766
try {
752767
Class<? extends Module> jdk8ModuleClass = (Class<? extends Module>)
753768
ClassUtils.forName("com.fasterxml.jackson.datatype.jdk8.Jdk8Module", this.moduleClassLoader);
754769
Module jdk8Module = BeanUtils.instantiateClass(jdk8ModuleClass);
755-
modulesToRegister.put(jdk8Module.getTypeId(), jdk8Module);
770+
modulesToRegister.set(jdk8Module.getTypeId(), jdk8Module);
756771
}
757772
catch (ClassNotFoundException ex) {
758773
// jackson-datatype-jdk8 not available
@@ -762,7 +777,7 @@ private void registerWellKnownModulesIfAvailable(Map<Object, Module> modulesToRe
762777
Class<? extends Module> javaTimeModuleClass = (Class<? extends Module>)
763778
ClassUtils.forName("com.fasterxml.jackson.datatype.jsr310.JavaTimeModule", this.moduleClassLoader);
764779
Module javaTimeModule = BeanUtils.instantiateClass(javaTimeModuleClass);
765-
modulesToRegister.put(javaTimeModule.getTypeId(), javaTimeModule);
780+
modulesToRegister.set(javaTimeModule.getTypeId(), javaTimeModule);
766781
}
767782
catch (ClassNotFoundException ex) {
768783
// jackson-datatype-jsr310 not available
@@ -774,7 +789,7 @@ private void registerWellKnownModulesIfAvailable(Map<Object, Module> modulesToRe
774789
Class<? extends Module> jodaModuleClass = (Class<? extends Module>)
775790
ClassUtils.forName("com.fasterxml.jackson.datatype.joda.JodaModule", this.moduleClassLoader);
776791
Module jodaModule = BeanUtils.instantiateClass(jodaModuleClass);
777-
modulesToRegister.put(jodaModule.getTypeId(), jodaModule);
792+
modulesToRegister.set(jodaModule.getTypeId(), jodaModule);
778793
}
779794
catch (ClassNotFoundException ex) {
780795
// jackson-datatype-joda not available
@@ -787,7 +802,7 @@ private void registerWellKnownModulesIfAvailable(Map<Object, Module> modulesToRe
787802
Class<? extends Module> kotlinModuleClass = (Class<? extends Module>)
788803
ClassUtils.forName("com.fasterxml.jackson.module.kotlin.KotlinModule", this.moduleClassLoader);
789804
Module kotlinModule = BeanUtils.instantiateClass(kotlinModuleClass);
790-
modulesToRegister.put(kotlinModule.getTypeId(), kotlinModule);
805+
modulesToRegister.set(kotlinModule.getTypeId(), kotlinModule);
791806
}
792807
catch (ClassNotFoundException ex) {
793808
if (!kotlinWarningLogged) {

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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import java.util.Map;
3434
import java.util.Optional;
3535
import java.util.TimeZone;
36+
import java.util.stream.StreamSupport;
3637

3738
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
3839
import com.fasterxml.jackson.annotation.JsonFilter;
@@ -329,6 +330,24 @@ public void overrideWellKnownModuleWithModule() throws IOException {
329330
assertNotNull(demoPojo.getOffsetDateTime());
330331
}
331332

333+
@Test // gh-22740
334+
public void registerMultipleModulesWithNullTypeId() {
335+
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
336+
SimpleModule fooModule = new SimpleModule();
337+
fooModule.addSerializer(new FooSerializer());
338+
SimpleModule barModule = new SimpleModule();
339+
barModule.addSerializer(new BarSerializer());
340+
builder.modulesToInstall(fooModule, barModule);
341+
ObjectMapper objectMapper = builder.build();
342+
assertEquals(1, StreamSupport
343+
.stream(getSerializerFactoryConfig(objectMapper).serializers().spliterator(), false)
344+
.filter(s -> s.findSerializer(null, SimpleType.construct(Foo.class), null) != null)
345+
.count());
346+
assertEquals(1, StreamSupport
347+
.stream(getSerializerFactoryConfig(objectMapper).serializers().spliterator(), false)
348+
.filter(s -> s.findSerializer(null, SimpleType.construct(Bar.class), null) != null)
349+
.count());
350+
}
332351

333352
private static SerializerFactoryConfig getSerializerFactoryConfig(ObjectMapper objectMapper) {
334353
return ((BasicSerializerFactory) objectMapper.getSerializerFactory()).getFactoryConfig();
@@ -679,4 +698,29 @@ public void setOffsetDateTime(OffsetDateTime offsetDateTime) {
679698
public static class MyXmlFactory extends XmlFactory {
680699
}
681700

701+
static class Foo {}
702+
703+
static class Bar {}
704+
705+
static class FooSerializer extends JsonSerializer<Foo> {
706+
@Override
707+
public void serialize(Foo value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
708+
}
709+
710+
@Override
711+
public Class<Foo> handledType() {
712+
return Foo.class;
713+
}
714+
}
715+
716+
static class BarSerializer extends JsonSerializer<Bar> {
717+
@Override
718+
public void serialize(Bar value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
719+
}
720+
@Override
721+
public Class<Bar> handledType() {
722+
return Bar.class;
723+
}
724+
}
725+
682726
}

0 commit comments

Comments
 (0)