Browse Source

Fix Jackson builder modulesToInstall override behavior

This commit updates Jackson2ObjectMapperBuilder in order
to ensure that modules specified via modulesToInstall
eventually override the default ones.

Closes gh-22624
pull/22632/head
Sebastien Deleuze 7 years ago
parent
commit
04223058f1
  1. 1
      build.gradle
  2. 48
      spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java
  3. 68
      spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilderTests.java

1
build.gradle

@ -727,6 +727,7 @@ project("spring-web") { @@ -727,6 +727,7 @@ project("spring-web") {
testCompile("com.fasterxml.jackson.datatype:jackson-datatype-joda:2.8.11")
testCompile("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.8.11")
testCompile("com.fasterxml.jackson.module:jackson-module-kotlin:2.8.11.1")
testCompile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.8.11")
testCompile("com.squareup.okhttp3:mockwebserver:${okhttp3Version}")
testRuntime("com.sun.mail:javax.mail:${javamailVersion}")
}

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -599,25 +599,32 @@ public class Jackson2ObjectMapperBuilder { @@ -599,25 +599,32 @@ public class Jackson2ObjectMapperBuilder {
public void configure(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
Map<Object, Module> modulesToRegister = new LinkedHashMap<Object, Module>();
if (this.findModulesViaServiceLoader) {
// Jackson 2.2+
objectMapper.registerModules(ObjectMapper.findModules(this.moduleClassLoader));
for (Module module : ObjectMapper.findModules(this.moduleClassLoader)) {
modulesToRegister.put(module.getTypeId(), module);
}
}
else if (this.findWellKnownModules) {
registerWellKnownModulesIfAvailable(objectMapper);
registerWellKnownModulesIfAvailable(modulesToRegister);
}
if (this.modules != null) {
for (Module module : this.modules) {
// Using Jackson 2.0+ registerModule method, not Jackson 2.2+ registerModules
objectMapper.registerModule(module);
modulesToRegister.put(module.getTypeId(), module);
}
}
if (this.moduleClasses != null) {
for (Class<? extends Module> module : this.moduleClasses) {
objectMapper.registerModule(BeanUtils.instantiate(module));
for (Class<? extends Module> moduleClass : this.moduleClasses) {
Module module = BeanUtils.instantiateClass(moduleClass);
modulesToRegister.put(module.getTypeId(), module);
}
}
// Using Jackson 2.0+ registerModule method, not Jackson 2.2+ registerModules
for (Module module : modulesToRegister.values()) {
objectMapper.registerModule(module);
}
if (this.dateFormat != null) {
objectMapper.setDateFormat(this.dateFormat);
@ -719,13 +726,14 @@ public class Jackson2ObjectMapperBuilder { @@ -719,13 +726,14 @@ public class Jackson2ObjectMapperBuilder {
}
@SuppressWarnings("unchecked")
private void registerWellKnownModulesIfAvailable(ObjectMapper objectMapper) {
private void registerWellKnownModulesIfAvailable(Map<Object, Module> modulesToRegister) {
// Java 7 java.nio.file.Path class present?
if (ClassUtils.isPresent("java.nio.file.Path", this.moduleClassLoader)) {
try {
Class<? extends Module> jdk7Module = (Class<? extends Module>)
Class<? extends Module> jdk7ModuleClass = (Class<? extends Module>)
ClassUtils.forName("com.fasterxml.jackson.datatype.jdk7.Jdk7Module", this.moduleClassLoader);
objectMapper.registerModule(BeanUtils.instantiateClass(jdk7Module));
Module jdk7Module = BeanUtils.instantiateClass(jdk7ModuleClass);
modulesToRegister.put(jdk7Module.getTypeId(), jdk7Module);
}
catch (ClassNotFoundException ex) {
// jackson-datatype-jdk7 not available
@ -735,9 +743,10 @@ public class Jackson2ObjectMapperBuilder { @@ -735,9 +743,10 @@ public class Jackson2ObjectMapperBuilder {
// Java 8 java.util.Optional class present?
if (ClassUtils.isPresent("java.util.Optional", this.moduleClassLoader)) {
try {
Class<? extends Module> jdk8Module = (Class<? extends Module>)
Class<? extends Module> jdk8ModuleClass = (Class<? extends Module>)
ClassUtils.forName("com.fasterxml.jackson.datatype.jdk8.Jdk8Module", this.moduleClassLoader);
objectMapper.registerModule(BeanUtils.instantiateClass(jdk8Module));
Module jdk8Module = BeanUtils.instantiateClass(jdk8ModuleClass);
modulesToRegister.put(jdk8Module.getTypeId(), jdk8Module);
}
catch (ClassNotFoundException ex) {
// jackson-datatype-jdk8 not available
@ -747,9 +756,10 @@ public class Jackson2ObjectMapperBuilder { @@ -747,9 +756,10 @@ public class Jackson2ObjectMapperBuilder {
// Java 8 java.time package present?
if (ClassUtils.isPresent("java.time.LocalDate", this.moduleClassLoader)) {
try {
Class<? extends Module> javaTimeModule = (Class<? extends Module>)
Class<? extends Module> javaTimeModuleClass = (Class<? extends Module>)
ClassUtils.forName("com.fasterxml.jackson.datatype.jsr310.JavaTimeModule", this.moduleClassLoader);
objectMapper.registerModule(BeanUtils.instantiateClass(javaTimeModule));
Module javaTimeModule = BeanUtils.instantiateClass(javaTimeModuleClass);
modulesToRegister.put(javaTimeModule.getTypeId(), javaTimeModule);
}
catch (ClassNotFoundException ex) {
// jackson-datatype-jsr310 not available
@ -759,9 +769,10 @@ public class Jackson2ObjectMapperBuilder { @@ -759,9 +769,10 @@ public class Jackson2ObjectMapperBuilder {
// Joda-Time present?
if (ClassUtils.isPresent("org.joda.time.LocalDate", this.moduleClassLoader)) {
try {
Class<? extends Module> jodaModule = (Class<? extends Module>)
Class<? extends Module> jodaModuleClass = (Class<? extends Module>)
ClassUtils.forName("com.fasterxml.jackson.datatype.joda.JodaModule", this.moduleClassLoader);
objectMapper.registerModule(BeanUtils.instantiateClass(jodaModule));
Module jodaModule = BeanUtils.instantiateClass(jodaModuleClass);
modulesToRegister.put(jodaModule.getTypeId(), jodaModule);
}
catch (ClassNotFoundException ex) {
// jackson-datatype-joda not available
@ -771,9 +782,10 @@ public class Jackson2ObjectMapperBuilder { @@ -771,9 +782,10 @@ public class Jackson2ObjectMapperBuilder {
// Kotlin present?
if (ClassUtils.isPresent("kotlin.Unit", this.moduleClassLoader)) {
try {
Class<? extends Module> kotlinModule = (Class<? extends Module>)
Class<? extends Module> kotlinModuleClass = (Class<? extends Module>)
ClassUtils.forName("com.fasterxml.jackson.module.kotlin.KotlinModule", this.moduleClassLoader);
objectMapper.registerModule(BeanUtils.instantiateClass(kotlinModule));
Module kotlinModule = BeanUtils.instantiateClass(kotlinModuleClass);
modulesToRegister.put(kotlinModule.getTypeId(), kotlinModule);
}
catch (ClassNotFoundException ex) {
// jackson-module-kotlin not available

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -21,6 +21,8 @@ import java.io.UnsupportedEncodingException; @@ -21,6 +21,8 @@ import java.io.UnsupportedEncodingException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.time.OffsetDateTime;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -38,6 +40,7 @@ import com.fasterxml.jackson.core.JsonGenerator; @@ -38,6 +40,7 @@ import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
@ -48,6 +51,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; @@ -48,6 +51,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.cfg.DeserializerFactoryConfig;
import com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig;
import com.fasterxml.jackson.databind.deser.BasicDeserializerFactory;
@ -64,12 +68,14 @@ import com.fasterxml.jackson.databind.ser.std.ClassSerializer; @@ -64,12 +68,14 @@ import com.fasterxml.jackson.databind.ser.std.ClassSerializer;
import com.fasterxml.jackson.databind.ser.std.NumberSerializer;
import com.fasterxml.jackson.databind.type.SimpleType;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import kotlin.ranges.IntRange;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.junit.Test;
import org.springframework.beans.FatalBeanException;
import org.springframework.util.StringUtils;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
@ -84,6 +90,8 @@ public class Jackson2ObjectMapperBuilderTests { @@ -84,6 +90,8 @@ public class Jackson2ObjectMapperBuilderTests {
private static final String DATE_FORMAT = "yyyy-MM-dd";
private static final String DATA = "{\"offsetDateTime\": \"2020-01-01T00:00:00\"}";
@Test
public void settersWithNullValues() {
@ -289,6 +297,18 @@ public class Jackson2ObjectMapperBuilderTests { @@ -289,6 +297,18 @@ public class Jackson2ObjectMapperBuilderTests {
assertThat(new String(objectMapper.writeValueAsBytes(new Integer(4)), "UTF-8"), containsString("customid"));
}
@Test // gh-22576
public void overrideWellKnownModuleWithModule() throws IOException {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addDeserializer(OffsetDateTime.class, new OffsetDateTimeDeserializer());
builder.modulesToInstall(javaTimeModule);
builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
ObjectMapper objectMapper = builder.build();
DemoPojo demoPojo = objectMapper.readValue(DATA, DemoPojo.class);
assertNotNull(demoPojo.getOffsetDateTime());
}
private static SerializerFactoryConfig getSerializerFactoryConfig(ObjectMapper objectMapper) {
return ((BasicSerializerFactory) objectMapper.getSerializerFactory()).getFactoryConfig();
@ -540,4 +560,50 @@ public class Jackson2ObjectMapperBuilderTests { @@ -540,4 +560,50 @@ public class Jackson2ObjectMapperBuilderTests {
}
}
public static class JacksonVisibilityBean {
private String property1;
public String property2;
public String getProperty3() {
return null;
}
}
static class OffsetDateTimeDeserializer extends JsonDeserializer<OffsetDateTime> {
private static final String CURRENT_ZONE_OFFSET = OffsetDateTime.now().getOffset().toString();
@Override
public OffsetDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
final String value = jsonParser.getValueAsString();
if (StringUtils.isEmpty(value)) {
return null;
}
try {
return OffsetDateTime.parse(value);
} catch (DateTimeParseException exception) {
return OffsetDateTime.parse(value + CURRENT_ZONE_OFFSET);
}
}
}
@JsonDeserialize
static class DemoPojo {
private OffsetDateTime offsetDateTime;
public OffsetDateTime getOffsetDateTime() {
return offsetDateTime;
}
public void setOffsetDateTime(OffsetDateTime offsetDateTime) {
this.offsetDateTime = offsetDateTime;
}
}
}

Loading…
Cancel
Save