DefaultJackson2Customizer.java
package top.infra.jackson2;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.beans.BeanUtils;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.util.ClassUtils;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.TimeZone;
/**
* see: {@link org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration}.
* see: org.springframework.hateoas.config.HypermediaSupportBeanDefinitionRegistrar
* see: org.springframework.hateoas.config.EnableHypermediaSupport
*/
public class DefaultJackson2Customizer implements Jackson2Customizer {
@SuppressWarnings("unchecked")
@Override
public void customize(final Jackson2Properties properties, final ObjectMapper mapper) {
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY,
this.feature(properties, DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, TRUE));
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
this.feature(properties, DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, FALSE));
mapper.configure(JsonGenerator.Feature.QUOTE_NON_NUMERIC_NUMBERS,
this.feature(properties, JsonGenerator.Feature.QUOTE_NON_NUMERIC_NUMBERS, FALSE));
mapper.configure(JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS,
this.feature(properties, JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS, TRUE));
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES,
this.feature(properties, JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, TRUE));
// see: https://github.com/FasterXML/jackson-databind/issues/1218
// since jackson2 2.9.x
//mapper.configure(MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES,
// this.feature(properties, MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES, FALSE));
mapper.configure(SerializationFeature.INDENT_OUTPUT,
this.feature(properties, SerializationFeature.INDENT_OUTPUT, FALSE));
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,
this.feature(properties, SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, FALSE));
//WRITE_DATES_WITH_ZONE_ID
mapper.setTimeZone(this.timeZone(properties));
this.configureDateFormat(properties, mapper);
// Jackson2ObjectMapperBuilder.registerWellKnownModulesIfAvailable
new Jackson2ObjectMapperBuilder().configure(mapper);
final ClassLoader moduleClassLoader = getClass().getClassLoader();
if (isGuavaPresent(moduleClassLoader)) {
try {
final Class<? extends Module> guavaModuleClass = (Class<? extends Module>)
ClassUtils.forName("com.fasterxml.jackson.datatype.guava.GuavaModule", moduleClassLoader);
final Module guavaModule = BeanUtils.instantiateClass(guavaModuleClass);
mapper.registerModule(guavaModule);
} catch (final ClassNotFoundException ex) {
// jackson-datatype-guava or guava not available
}
}
}
@SuppressWarnings("unchecked")
@Override
public void customize(final Jackson2Properties properties, final Jackson2ObjectMapperBuilder builder) {
this.feature(builder, DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY,
this.feature(properties, DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, TRUE));
this.feature(builder, DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
this.feature(properties, DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, FALSE));
this.feature(builder, JsonGenerator.Feature.QUOTE_NON_NUMERIC_NUMBERS,
this.feature(properties, JsonGenerator.Feature.QUOTE_NON_NUMERIC_NUMBERS, FALSE));
this.feature(builder, JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS,
this.feature(properties, JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS, TRUE));
this.feature(builder, JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES,
this.feature(properties, JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, TRUE));
// see: https://github.com/FasterXML/jackson-databind/issues/1218
// since jackson2 2.9.x
//this.feature(builder, MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES,
// this.feature(properties, MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES, FALSE));
this.feature(builder, SerializationFeature.INDENT_OUTPUT,
this.feature(properties, SerializationFeature.INDENT_OUTPUT, FALSE));
this.feature(builder, SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,
this.feature(properties, SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, FALSE));
//WRITE_DATES_WITH_ZONE_ID
builder.timeZone(this.timeZone(properties));
properties.setDateFormat(this.dateFormat(properties));
final ClassLoader moduleClassLoader = this.moduleClassLoader(builder);
if (isGuavaPresent(moduleClassLoader)) {
try {
final Class<? extends Module> guavaModuleClass = (Class<? extends Module>)
ClassUtils.forName("com.fasterxml.jackson.datatype.guava.GuavaModule", moduleClassLoader);
final Module guavaModule = BeanUtils.instantiateClass(guavaModuleClass);
final List<Module> modules = this.modules(builder);
modules.add(guavaModule);
builder.modulesToInstall(modules.toArray(new Module[0]));
} catch (final ClassNotFoundException ex) {
// jackson-datatype-guava or guava not available
}
}
}
public boolean isGuavaPresent(final ClassLoader classLoader) {
return ClassUtils.isPresent("com.google.common.collect.Multimap", classLoader)
&& ClassUtils.isPresent("com.fasterxml.jackson.datatype.guava.GuavaModule", classLoader);
}
/**
* see: {@link org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration}
* Jackson2ObjectMapperBuilderCustomizerConfiguration#configureDateFormat
*/
private void configureDateFormat(final Jackson2Properties properties, final ObjectMapper mapper) {
// We support a fully qualified class name extending DateFormat or a date
// pattern string value
String dateFormat = properties.getDateFormat();
if (dateFormat != null) {
try {
Class<?> dateFormatClass = ClassUtils.forName(dateFormat, null);
mapper.setDateFormat((DateFormat) BeanUtils.instantiateClass(dateFormatClass));
} catch (ClassNotFoundException ex) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(
dateFormat);
// Since Jackson 2.6.3 we always need to set a TimeZone (see
// gh-4170). If none in our properties fallback to the Jackson's
// default
TimeZone timeZone = properties.getTimeZone();
if (timeZone == null) {
timeZone = new ObjectMapper().getSerializationConfig()
.getTimeZone();
}
simpleDateFormat.setTimeZone(timeZone);
mapper.setDateFormat(simpleDateFormat);
}
}
}
private void feature(final Jackson2ObjectMapperBuilder builder, final Object feature, final Boolean enabled) {
if (enabled) {
builder.featuresToEnable(feature);
} else {
builder.featuresToDisable(feature);
}
}
Boolean feature(final Jackson2Properties properties, final DeserializationFeature feature, final Boolean defaultValue) {
return properties.getDeserialization().getOrDefault(feature, defaultValue);
}
Boolean feature(final Jackson2Properties properties, final JsonGenerator.Feature feature, final Boolean defaultValue) {
return properties.getGenerator().getOrDefault(feature, defaultValue);
}
Boolean feature(final Jackson2Properties properties, final JsonParser.Feature feature, final Boolean defaultValue) {
return properties.getParser().getOrDefault(feature, defaultValue);
}
Boolean feature(final Jackson2Properties properties, final MapperFeature feature, final Boolean defaultValue) {
return properties.getMapper().getOrDefault(feature, defaultValue);
}
Boolean feature(final Jackson2Properties properties, final SerializationFeature feature, final Boolean defaultValue) {
return properties.getSerialization().getOrDefault(feature, defaultValue);
}
}