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
This commit is contained in:
@@ -18,6 +18,7 @@ package org.springframework.http.converter.json;
|
|||||||
|
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
@@ -62,6 +63,8 @@ import org.springframework.http.HttpLogging;
|
|||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.util.xml.StaxUtils;
|
import org.springframework.util.xml.StaxUtils;
|
||||||
|
|
||||||
@@ -632,24 +635,27 @@ public class Jackson2ObjectMapperBuilder {
|
|||||||
public void configure(ObjectMapper objectMapper) {
|
public void configure(ObjectMapper objectMapper) {
|
||||||
Assert.notNull(objectMapper, "ObjectMapper must not be null");
|
Assert.notNull(objectMapper, "ObjectMapper must not be null");
|
||||||
|
|
||||||
Map<Object, Module> modulesToRegister = new LinkedHashMap<>();
|
MultiValueMap<Object, Module> modulesToRegister = new LinkedMultiValueMap<>();
|
||||||
if (this.findModulesViaServiceLoader) {
|
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) {
|
else if (this.findWellKnownModules) {
|
||||||
registerWellKnownModulesIfAvailable(modulesToRegister);
|
registerWellKnownModulesIfAvailable(modulesToRegister);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.modules != null) {
|
if (this.modules != null) {
|
||||||
this.modules.forEach(module -> modulesToRegister.put(module.getTypeId(), module));
|
this.modules.forEach(module -> registerModule(module, modulesToRegister));
|
||||||
}
|
}
|
||||||
if (this.moduleClasses != null) {
|
if (this.moduleClasses != null) {
|
||||||
for (Class<? extends Module> moduleClass : this.moduleClasses) {
|
for (Class<? extends Module> moduleClass : this.moduleClasses) {
|
||||||
Module module = BeanUtils.instantiateClass(moduleClass);
|
registerModule(BeanUtils.instantiateClass(moduleClass), modulesToRegister);
|
||||||
modulesToRegister.put(module.getTypeId(), module);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
objectMapper.registerModules(modulesToRegister.values());
|
List<Module> modules = new ArrayList<>();
|
||||||
|
for (List<Module> nestedModules : modulesToRegister.values()) {
|
||||||
|
modules.addAll(nestedModules);
|
||||||
|
}
|
||||||
|
objectMapper.registerModules(modules);
|
||||||
|
|
||||||
if (this.dateFormat != null) {
|
if (this.dateFormat != null) {
|
||||||
objectMapper.setDateFormat(this.dateFormat);
|
objectMapper.setDateFormat(this.dateFormat);
|
||||||
@@ -701,6 +707,15 @@ public class Jackson2ObjectMapperBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void registerModule(Module module, MultiValueMap<Object, Module> 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
|
// Any change to this method should be also applied to spring-jms and spring-messaging
|
||||||
// MappingJackson2MessageConverter default constructors
|
// MappingJackson2MessageConverter default constructors
|
||||||
@@ -747,12 +762,12 @@ public class Jackson2ObjectMapperBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void registerWellKnownModulesIfAvailable(Map<Object, Module> modulesToRegister) {
|
private void registerWellKnownModulesIfAvailable(MultiValueMap<Object, Module> modulesToRegister) {
|
||||||
try {
|
try {
|
||||||
Class<? extends Module> jdk8ModuleClass = (Class<? extends Module>)
|
Class<? extends Module> jdk8ModuleClass = (Class<? extends Module>)
|
||||||
ClassUtils.forName("com.fasterxml.jackson.datatype.jdk8.Jdk8Module", this.moduleClassLoader);
|
ClassUtils.forName("com.fasterxml.jackson.datatype.jdk8.Jdk8Module", this.moduleClassLoader);
|
||||||
Module jdk8Module = BeanUtils.instantiateClass(jdk8ModuleClass);
|
Module jdk8Module = BeanUtils.instantiateClass(jdk8ModuleClass);
|
||||||
modulesToRegister.put(jdk8Module.getTypeId(), jdk8Module);
|
modulesToRegister.set(jdk8Module.getTypeId(), jdk8Module);
|
||||||
}
|
}
|
||||||
catch (ClassNotFoundException ex) {
|
catch (ClassNotFoundException ex) {
|
||||||
// jackson-datatype-jdk8 not available
|
// jackson-datatype-jdk8 not available
|
||||||
@@ -762,7 +777,7 @@ public class Jackson2ObjectMapperBuilder {
|
|||||||
Class<? extends Module> javaTimeModuleClass = (Class<? extends Module>)
|
Class<? extends Module> javaTimeModuleClass = (Class<? extends Module>)
|
||||||
ClassUtils.forName("com.fasterxml.jackson.datatype.jsr310.JavaTimeModule", this.moduleClassLoader);
|
ClassUtils.forName("com.fasterxml.jackson.datatype.jsr310.JavaTimeModule", this.moduleClassLoader);
|
||||||
Module javaTimeModule = BeanUtils.instantiateClass(javaTimeModuleClass);
|
Module javaTimeModule = BeanUtils.instantiateClass(javaTimeModuleClass);
|
||||||
modulesToRegister.put(javaTimeModule.getTypeId(), javaTimeModule);
|
modulesToRegister.set(javaTimeModule.getTypeId(), javaTimeModule);
|
||||||
}
|
}
|
||||||
catch (ClassNotFoundException ex) {
|
catch (ClassNotFoundException ex) {
|
||||||
// jackson-datatype-jsr310 not available
|
// jackson-datatype-jsr310 not available
|
||||||
@@ -774,7 +789,7 @@ public class Jackson2ObjectMapperBuilder {
|
|||||||
Class<? extends Module> jodaModuleClass = (Class<? extends Module>)
|
Class<? extends Module> jodaModuleClass = (Class<? extends Module>)
|
||||||
ClassUtils.forName("com.fasterxml.jackson.datatype.joda.JodaModule", this.moduleClassLoader);
|
ClassUtils.forName("com.fasterxml.jackson.datatype.joda.JodaModule", this.moduleClassLoader);
|
||||||
Module jodaModule = BeanUtils.instantiateClass(jodaModuleClass);
|
Module jodaModule = BeanUtils.instantiateClass(jodaModuleClass);
|
||||||
modulesToRegister.put(jodaModule.getTypeId(), jodaModule);
|
modulesToRegister.set(jodaModule.getTypeId(), jodaModule);
|
||||||
}
|
}
|
||||||
catch (ClassNotFoundException ex) {
|
catch (ClassNotFoundException ex) {
|
||||||
// jackson-datatype-joda not available
|
// jackson-datatype-joda not available
|
||||||
@@ -787,7 +802,7 @@ public class Jackson2ObjectMapperBuilder {
|
|||||||
Class<? extends Module> kotlinModuleClass = (Class<? extends Module>)
|
Class<? extends Module> kotlinModuleClass = (Class<? extends Module>)
|
||||||
ClassUtils.forName("com.fasterxml.jackson.module.kotlin.KotlinModule", this.moduleClassLoader);
|
ClassUtils.forName("com.fasterxml.jackson.module.kotlin.KotlinModule", this.moduleClassLoader);
|
||||||
Module kotlinModule = BeanUtils.instantiateClass(kotlinModuleClass);
|
Module kotlinModule = BeanUtils.instantiateClass(kotlinModuleClass);
|
||||||
modulesToRegister.put(kotlinModule.getTypeId(), kotlinModule);
|
modulesToRegister.set(kotlinModule.getTypeId(), kotlinModule);
|
||||||
}
|
}
|
||||||
catch (ClassNotFoundException ex) {
|
catch (ClassNotFoundException ex) {
|
||||||
if (!kotlinWarningLogged) {
|
if (!kotlinWarningLogged) {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import java.util.Locale;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
|
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
|
||||||
import com.fasterxml.jackson.annotation.JsonFilter;
|
import com.fasterxml.jackson.annotation.JsonFilter;
|
||||||
@@ -329,6 +330,24 @@ public class Jackson2ObjectMapperBuilderTests {
|
|||||||
assertNotNull(demoPojo.getOffsetDateTime());
|
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) {
|
private static SerializerFactoryConfig getSerializerFactoryConfig(ObjectMapper objectMapper) {
|
||||||
return ((BasicSerializerFactory) objectMapper.getSerializerFactory()).getFactoryConfig();
|
return ((BasicSerializerFactory) objectMapper.getSerializerFactory()).getFactoryConfig();
|
||||||
@@ -679,4 +698,29 @@ public class Jackson2ObjectMapperBuilderTests {
|
|||||||
public static class MyXmlFactory extends XmlFactory {
|
public static class MyXmlFactory extends XmlFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class Foo {}
|
||||||
|
|
||||||
|
static class Bar {}
|
||||||
|
|
||||||
|
static class FooSerializer extends JsonSerializer<Foo> {
|
||||||
|
@Override
|
||||||
|
public void serialize(Foo value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<Foo> handledType() {
|
||||||
|
return Foo.class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class BarSerializer extends JsonSerializer<Bar> {
|
||||||
|
@Override
|
||||||
|
public void serialize(Bar value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Class<Bar> handledType() {
|
||||||
|
return Bar.class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user