Add support for configuring Jackson's ConstructorDetector
Closes gh-27178
This commit is contained in:
@@ -34,6 +34,7 @@ import com.fasterxml.jackson.databind.Module;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.cfg.ConstructorDetector;
|
||||
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
@@ -41,6 +42,7 @@ import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonProperties.ConstructorDetectorStrategy;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.jackson.JsonComponentModule;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -188,6 +190,7 @@ public class JacksonAutoConfiguration {
|
||||
configureModules(builder);
|
||||
configureLocale(builder);
|
||||
configureDefaultLeniency(builder);
|
||||
configureConstructorDetector(builder);
|
||||
}
|
||||
|
||||
private void configureFeatures(Jackson2ObjectMapperBuilder builder, Map<?, Boolean> features) {
|
||||
@@ -297,6 +300,27 @@ public class JacksonAutoConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
private void configureConstructorDetector(Jackson2ObjectMapperBuilder builder) {
|
||||
ConstructorDetectorStrategy strategy = this.jacksonProperties.getConstructorDetector();
|
||||
if (strategy != null) {
|
||||
builder.postConfigurer((objectMapper) -> {
|
||||
switch (strategy) {
|
||||
case USE_PROPERTIES_BASED:
|
||||
objectMapper.setConstructorDetector(ConstructorDetector.USE_PROPERTIES_BASED);
|
||||
break;
|
||||
case USE_DELEGATING:
|
||||
objectMapper.setConstructorDetector(ConstructorDetector.USE_DELEGATING);
|
||||
break;
|
||||
case EXPLICIT_ONLY:
|
||||
objectMapper.setConstructorDetector(ConstructorDetector.EXPLICIT_ONLY);
|
||||
break;
|
||||
default:
|
||||
objectMapper.setConstructorDetector(ConstructorDetector.DEFAULT);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> Collection<T> getBeans(ListableBeanFactory beanFactory, Class<T> type) {
|
||||
return BeanFactoryUtils.beansOfTypeIncludingAncestors(beanFactory, type).values();
|
||||
}
|
||||
|
||||
@@ -97,6 +97,12 @@ public class JacksonProperties {
|
||||
*/
|
||||
private Boolean defaultLeniency;
|
||||
|
||||
/**
|
||||
* Strategy to use to to auto-detect constructor, and in particular behavior with
|
||||
* single-argument constructors.
|
||||
*/
|
||||
private ConstructorDetectorStrategy constructorDetector;
|
||||
|
||||
/**
|
||||
* Time zone used when formatting dates. For instance, "America/Los_Angeles" or
|
||||
* "GMT+10".
|
||||
@@ -164,6 +170,14 @@ public class JacksonProperties {
|
||||
this.defaultLeniency = defaultLeniency;
|
||||
}
|
||||
|
||||
public ConstructorDetectorStrategy getConstructorDetector() {
|
||||
return this.constructorDetector;
|
||||
}
|
||||
|
||||
public void setConstructorDetector(ConstructorDetectorStrategy constructorDetector) {
|
||||
this.constructorDetector = constructorDetector;
|
||||
}
|
||||
|
||||
public TimeZone getTimeZone() {
|
||||
return this.timeZone;
|
||||
}
|
||||
@@ -180,4 +194,29 @@ public class JacksonProperties {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
public enum ConstructorDetectorStrategy {
|
||||
|
||||
/**
|
||||
* Use heuristics to see if "properties" mode is to be used.
|
||||
*/
|
||||
DEFAULT,
|
||||
|
||||
/**
|
||||
* Assume "properties" mode if not explicitly annotated otherwise.
|
||||
*/
|
||||
USE_PROPERTIES_BASED,
|
||||
|
||||
/**
|
||||
* Assume "delegating" mode if not explicitly annotated otherwise.
|
||||
*/
|
||||
USE_DELEGATING,
|
||||
|
||||
/**
|
||||
* Refuse to decide implicit mode and instead throw an InvalidDefinitionException
|
||||
* for ambiguous cases.
|
||||
*/
|
||||
EXPLICIT_ONLY;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1048,6 +1048,10 @@
|
||||
"name": "spring.integration.jdbc.initialize-schema",
|
||||
"defaultValue": "embedded"
|
||||
},
|
||||
{
|
||||
"name": "spring.jackson.constructor-detector",
|
||||
"defaultValue": "default"
|
||||
},
|
||||
{
|
||||
"name": "spring.jackson.joda-date-time-format",
|
||||
"type": "java.lang.String",
|
||||
|
||||
@@ -54,4 +54,25 @@ public class Jackson211AutoConfigurationTests extends JacksonAutoConfigurationTe
|
||||
});
|
||||
}
|
||||
|
||||
// ConstructorDetector only available as of Jackson 2.12
|
||||
@Override
|
||||
void constructorDetectorWithNoStrategyUseDefault() {
|
||||
}
|
||||
|
||||
@Override
|
||||
void constructorDetectorWithDefaultStrategy() {
|
||||
}
|
||||
|
||||
@Override
|
||||
void constructorDetectorWithUsePropertiesBasedStrategy() {
|
||||
}
|
||||
|
||||
@Override
|
||||
void constructorDetectorWithUseDelegatingStrategy() {
|
||||
}
|
||||
|
||||
@Override
|
||||
void constructorDetectorWithExplicitOnlyStrategy() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.cfg.ConstructorDetector;
|
||||
import com.fasterxml.jackson.databind.cfg.ConstructorDetector.SingleArgConstructor;
|
||||
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.databind.util.StdDateFormat;
|
||||
@@ -323,6 +325,62 @@ class JacksonAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorDetectorWithNoStrategyUseDefault() {
|
||||
this.contextRunner.run((context) -> {
|
||||
ObjectMapper mapper = context.getBean(ObjectMapper.class);
|
||||
ConstructorDetector cd = mapper.getDeserializationConfig().getConstructorDetector();
|
||||
assertThat(cd.singleArgMode()).isEqualTo(SingleArgConstructor.HEURISTIC);
|
||||
assertThat(cd.requireCtorAnnotation()).isFalse();
|
||||
assertThat(cd.allowJDKTypeConstructors()).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorDetectorWithDefaultStrategy() {
|
||||
this.contextRunner.withPropertyValues("spring.jackson.constructor-detector=default").run((context) -> {
|
||||
ObjectMapper mapper = context.getBean(ObjectMapper.class);
|
||||
ConstructorDetector cd = mapper.getDeserializationConfig().getConstructorDetector();
|
||||
assertThat(cd.singleArgMode()).isEqualTo(SingleArgConstructor.HEURISTIC);
|
||||
assertThat(cd.requireCtorAnnotation()).isFalse();
|
||||
assertThat(cd.allowJDKTypeConstructors()).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorDetectorWithUsePropertiesBasedStrategy() {
|
||||
this.contextRunner.withPropertyValues("spring.jackson.constructor-detector=use-properties-based")
|
||||
.run((context) -> {
|
||||
ObjectMapper mapper = context.getBean(ObjectMapper.class);
|
||||
ConstructorDetector cd = mapper.getDeserializationConfig().getConstructorDetector();
|
||||
assertThat(cd.singleArgMode()).isEqualTo(SingleArgConstructor.PROPERTIES);
|
||||
assertThat(cd.requireCtorAnnotation()).isFalse();
|
||||
assertThat(cd.allowJDKTypeConstructors()).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorDetectorWithUseDelegatingStrategy() {
|
||||
this.contextRunner.withPropertyValues("spring.jackson.constructor-detector=use-delegating").run((context) -> {
|
||||
ObjectMapper mapper = context.getBean(ObjectMapper.class);
|
||||
ConstructorDetector cd = mapper.getDeserializationConfig().getConstructorDetector();
|
||||
assertThat(cd.singleArgMode()).isEqualTo(SingleArgConstructor.DELEGATING);
|
||||
assertThat(cd.requireCtorAnnotation()).isFalse();
|
||||
assertThat(cd.allowJDKTypeConstructors()).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorDetectorWithExplicitOnlyStrategy() {
|
||||
this.contextRunner.withPropertyValues("spring.jackson.constructor-detector=explicit-only").run((context) -> {
|
||||
ObjectMapper mapper = context.getBean(ObjectMapper.class);
|
||||
ConstructorDetector cd = mapper.getDeserializationConfig().getConstructorDetector();
|
||||
assertThat(cd.singleArgMode()).isEqualTo(SingleArgConstructor.REQUIRE_MODE);
|
||||
assertThat(cd.requireCtorAnnotation()).isFalse();
|
||||
assertThat(cd.allowJDKTypeConstructors()).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void additionalJacksonBuilderCustomization() {
|
||||
this.contextRunner.withUserConfiguration(ObjectMapperBuilderCustomConfig.class).run((context) -> {
|
||||
|
||||
Reference in New Issue
Block a user