Commit 286a3bb1 authored by Phillip Webb's avatar Phillip Webb

Polish GSON customization support

Closes gh-11498
parent ba552f1d
/* /*
* Copyright 2012-2014 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -18,18 +18,16 @@ package org.springframework.boot.autoconfigure.gson; ...@@ -18,18 +18,16 @@ package org.springframework.boot.autoconfigure.gson;
import java.util.List; import java.util.List;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.LongSerializationPolicy;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
/** /**
...@@ -41,120 +39,65 @@ import org.springframework.core.Ordered; ...@@ -41,120 +39,65 @@ import org.springframework.core.Ordered;
*/ */
@Configuration @Configuration
@ConditionalOnClass(Gson.class) @ConditionalOnClass(Gson.class)
@EnableConfigurationProperties(GsonProperties.class)
public class GsonAutoConfiguration { public class GsonAutoConfiguration {
@Configuration @Bean
static class GsonConfiguration { public GsonBuilder gsonBuilder(List<GsonBuilderCustomizer> customizers) {
GsonBuilder builder = new GsonBuilder();
@Bean customizers.forEach(c -> c.customize(builder));
@Primary return builder;
@ConditionalOnMissingBean(Gson.class)
public Gson gson(GsonBuilder gsonBuilder) {
return gsonBuilder.create();
}
} }
@Configuration @Bean
static class GsonBuilderConfiguration { @ConditionalOnMissingBean(Gson.class)
public Gson gson(GsonBuilder gsonBuilder) {
@Bean return gsonBuilder.create();
public GsonBuilder gsonBuilder(List<GsonBuilderCustomizer> customizers) {
final GsonBuilder gsonBuilder = new GsonBuilder();
customizers.forEach(c -> c.customize(gsonBuilder));
return gsonBuilder;
}
} }
@Configuration @Bean
@EnableConfigurationProperties(GsonProperties.class) public StandardGsonBuilderCustomizer standardGsonBuilderCustomizer(
static class GsonBuilderCustomizerConfiguration { GsonProperties gsonProperties) {
return new StandardGsonBuilderCustomizer(gsonProperties);
@Bean }
public StandardGsonBuilderCustomizer standardGsonBuilderCustomizer(
GsonProperties gsonProperties) {
return new StandardGsonBuilderCustomizer(gsonProperties);
}
private static final class StandardGsonBuilderCustomizer
implements GsonBuilderCustomizer, Ordered {
private final GsonProperties properties;
StandardGsonBuilderCustomizer(GsonProperties properties) {
this.properties = properties;
}
@Override
public int getOrder() {
return 0;
}
@Override
public void customize(GsonBuilder gsonBuilder) {
boolean generateNonExecutableJson = this.properties
.isGenerateNonExecutableJson();
if (generateNonExecutableJson) {
gsonBuilder.generateNonExecutableJson();
}
boolean excludeFieldsWithoutExposeAnnotation = this.properties
.isExcludeFieldsWithoutExposeAnnotation();
if (excludeFieldsWithoutExposeAnnotation) {
gsonBuilder.excludeFieldsWithoutExposeAnnotation();
}
boolean serializeNulls = this.properties.isSerializeNulls();
if (serializeNulls) {
gsonBuilder.serializeNulls();
}
boolean enableComplexMapKeySerialization = this.properties
.isEnableComplexMapKeySerialization();
if (enableComplexMapKeySerialization) {
gsonBuilder.enableComplexMapKeySerialization();
}
boolean disableInnerClassSerialization = this.properties
.isDisableInnerClassSerialization();
if (disableInnerClassSerialization) {
gsonBuilder.disableInnerClassSerialization();
}
LongSerializationPolicy longSerializationPolicy = this.properties
.getLongSerializationPolicy();
if (longSerializationPolicy != null) {
gsonBuilder.setLongSerializationPolicy(longSerializationPolicy);
}
FieldNamingPolicy fieldNamingPolicy = this.properties private static final class StandardGsonBuilderCustomizer
.getFieldNamingPolicy(); implements GsonBuilderCustomizer, Ordered {
if (fieldNamingPolicy != null) {
gsonBuilder.setFieldNamingPolicy(fieldNamingPolicy);
}
boolean prettyPrinting = this.properties.isPrettyPrinting(); private final GsonProperties properties;
if (prettyPrinting) {
gsonBuilder.setPrettyPrinting();
}
boolean isLenient = this.properties.isLenient(); StandardGsonBuilderCustomizer(GsonProperties properties) {
if (isLenient) { this.properties = properties;
gsonBuilder.setLenient(); }
}
boolean disableHtmlEscaping = this.properties.isDisableHtmlEscaping(); @Override
if (disableHtmlEscaping) { public int getOrder() {
gsonBuilder.disableHtmlEscaping(); return 0;
} }
String dateFormat = this.properties.getDateFormat(); @Override
if (dateFormat != null) { public void customize(GsonBuilder builder) {
gsonBuilder.setDateFormat(dateFormat); GsonProperties properties = this.properties;
} PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
} map.from(properties::getGenerateNonExecutableJson)
.toCall(builder::generateNonExecutableJson);
map.from(properties::getExcludeFieldsWithoutExposeAnnotation)
.toCall(builder::excludeFieldsWithoutExposeAnnotation);
map.from(properties::getSerializeNulls).toCall(builder::serializeNulls);
map.from(properties::getEnableComplexMapKeySerialization)
.toCall(builder::enableComplexMapKeySerialization);
map.from(properties::getDisableInnerClassSerialization)
.toCall(builder::disableInnerClassSerialization);
map.from(properties::getLongSerializationPolicy)
.to(builder::setLongSerializationPolicy);
map.from(properties::getFieldNamingPolicy).to(builder::setFieldNamingPolicy);
map.from(properties::getPrettyPrinting).toCall(builder::setPrettyPrinting);
map.from(properties::getLenient).toCall(builder::setLenient);
map.from(properties::getDisableHtmlEscaping)
.toCall(builder::disableHtmlEscaping);
map.from(properties::getDateFormat).to(builder::setDateFormat);
} }
} }
} }
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
......
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -19,7 +19,6 @@ package org.springframework.boot.autoconfigure.gson; ...@@ -19,7 +19,6 @@ package org.springframework.boot.autoconfigure.gson;
import com.google.gson.FieldNamingPolicy; import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.LongSerializationPolicy; import com.google.gson.LongSerializationPolicy;
import com.google.gson.annotations.Expose;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
...@@ -33,109 +32,103 @@ import org.springframework.boot.context.properties.ConfigurationProperties; ...@@ -33,109 +32,103 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
public class GsonProperties { public class GsonProperties {
/** /**
* Makes the output JSON non-executable in Javascript by prefixing the generated JSON * Whether to generate non executable JSON by prefixing the output with some special
* with some special text. * text.
*/ */
private boolean generateNonExecutableJson; private Boolean generateNonExecutableJson;
/** /**
* Configures {@link Gson} to exclude all fields from consideration for serialization * Whether to exclude all fields from consideration for serialization or
* or deserialization that do not have the {@link Expose} annotation. * deserialization that do not have the "Expose" annotation.
*/ */
private boolean excludeFieldsWithoutExposeAnnotation; private Boolean excludeFieldsWithoutExposeAnnotation;
/** /**
* Configure {@link Gson} to serialize null fields. * Whether to to serialize null fields.
*/ */
private boolean serializeNulls; private Boolean serializeNulls;
/** /**
* Enabling this feature will only change the serialized form if the map key is a * Whether to enabled serialization of complex map keys (i.e. non-primitives).
* complex type (i.e. non-primitive) in its serialized JSON form
*/ */
private boolean enableComplexMapKeySerialization; private Boolean enableComplexMapKeySerialization;
/** /**
* Configures {@link Gson} to exclude inner classes during serialization. * Whether to exclude inner classes during serialization.
*/ */
private boolean disableInnerClassSerialization; private Boolean disableInnerClassSerialization;
/** /**
* Configures {@link Gson} to apply a specific serialization policy for Long and long * Serialization policy for Long and long types.
* objects.
*/ */
private LongSerializationPolicy longSerializationPolicy; private LongSerializationPolicy longSerializationPolicy;
/** /**
* Configures {@link Gson} to apply a specific naming policy to an object's field * The naming policy that should be applied to an object's field during serialization
* during serialization and deserialization. * and deserialization.
*/ */
private FieldNamingPolicy fieldNamingPolicy; private FieldNamingPolicy fieldNamingPolicy;
/** /**
* Configures {@link Gson} to output Json that fits in a page for pretty printing. * Whether to output serialized JSON that fits in a page for pretty printing.
* This option only affects Json serialization.
*/ */
private boolean prettyPrinting; private Boolean prettyPrinting;
/** /**
* By default, {@link Gson} is strict and only accepts JSON as specified by RFC 4627. * Whether to be lenient about parsing JSON that doesn't conform to RFC 4627.
* This option makes the parser liberal in what it accepts.
*/ */
private boolean lenient; private Boolean lenient;
/** /**
* By default, {@link Gson} escapes HTML characters such as < > etc. Use this option * Whether to disable the escaping of HTML characters such as '<' '>' etc.
* to configure Gson to pass-through HTML characters as is.
*/ */
private boolean disableHtmlEscaping; private Boolean disableHtmlEscaping;
/** /**
* Configures {@link Gson} to serialize Date objects according to the pattern * The format to use when serializing Date objects.
* provided.
*/ */
private String dateFormat; private String dateFormat;
public boolean isGenerateNonExecutableJson() { public Boolean getGenerateNonExecutableJson() {
return this.generateNonExecutableJson; return this.generateNonExecutableJson;
} }
public void setGenerateNonExecutableJson(boolean generateNonExecutableJson) { public void setGenerateNonExecutableJson(Boolean generateNonExecutableJson) {
this.generateNonExecutableJson = generateNonExecutableJson; this.generateNonExecutableJson = generateNonExecutableJson;
} }
public boolean isExcludeFieldsWithoutExposeAnnotation() { public Boolean getExcludeFieldsWithoutExposeAnnotation() {
return this.excludeFieldsWithoutExposeAnnotation; return this.excludeFieldsWithoutExposeAnnotation;
} }
public void setExcludeFieldsWithoutExposeAnnotation( public void setExcludeFieldsWithoutExposeAnnotation(
boolean excludeFieldsWithoutExposeAnnotation) { Boolean excludeFieldsWithoutExposeAnnotation) {
this.excludeFieldsWithoutExposeAnnotation = excludeFieldsWithoutExposeAnnotation; this.excludeFieldsWithoutExposeAnnotation = excludeFieldsWithoutExposeAnnotation;
} }
public boolean isSerializeNulls() { public Boolean getSerializeNulls() {
return this.serializeNulls; return this.serializeNulls;
} }
public void setSerializeNulls(boolean serializeNulls) { public void setSerializeNulls(Boolean serializeNulls) {
this.serializeNulls = serializeNulls; this.serializeNulls = serializeNulls;
} }
public boolean isEnableComplexMapKeySerialization() { public Boolean getEnableComplexMapKeySerialization() {
return this.enableComplexMapKeySerialization; return this.enableComplexMapKeySerialization;
} }
public void setEnableComplexMapKeySerialization( public void setEnableComplexMapKeySerialization(
boolean enableComplexMapKeySerialization) { Boolean enableComplexMapKeySerialization) {
this.enableComplexMapKeySerialization = enableComplexMapKeySerialization; this.enableComplexMapKeySerialization = enableComplexMapKeySerialization;
} }
public boolean isDisableInnerClassSerialization() { public Boolean getDisableInnerClassSerialization() {
return this.disableInnerClassSerialization; return this.disableInnerClassSerialization;
} }
public void setDisableInnerClassSerialization( public void setDisableInnerClassSerialization(
boolean disableInnerClassSerialization) { Boolean disableInnerClassSerialization) {
this.disableInnerClassSerialization = disableInnerClassSerialization; this.disableInnerClassSerialization = disableInnerClassSerialization;
} }
...@@ -156,27 +149,27 @@ public class GsonProperties { ...@@ -156,27 +149,27 @@ public class GsonProperties {
this.fieldNamingPolicy = fieldNamingPolicy; this.fieldNamingPolicy = fieldNamingPolicy;
} }
public boolean isPrettyPrinting() { public Boolean getPrettyPrinting() {
return this.prettyPrinting; return this.prettyPrinting;
} }
public void setPrettyPrinting(boolean prettyPrinting) { public void setPrettyPrinting(Boolean prettyPrinting) {
this.prettyPrinting = prettyPrinting; this.prettyPrinting = prettyPrinting;
} }
public boolean isLenient() { public Boolean getLenient() {
return this.lenient; return this.lenient;
} }
public void setLenient(boolean lenient) { public void setLenient(Boolean lenient) {
this.lenient = lenient; this.lenient = lenient;
} }
public boolean isDisableHtmlEscaping() { public Boolean getDisableHtmlEscaping() {
return this.disableHtmlEscaping; return this.disableHtmlEscaping;
} }
public void setDisableHtmlEscaping(boolean disableHtmlEscaping) { public void setDisableHtmlEscaping(Boolean disableHtmlEscaping) {
this.disableHtmlEscaping = disableHtmlEscaping; this.disableHtmlEscaping = disableHtmlEscaping;
} }
...@@ -187,4 +180,5 @@ public class GsonProperties { ...@@ -187,4 +180,5 @@ public class GsonProperties {
public void setDateFormat(String dateFormat) { public void setDateFormat(String dateFormat) {
this.dateFormat = dateFormat; this.dateFormat = dateFormat;
} }
} }
/* /*
* Copyright 2012-2016 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -34,6 +34,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; ...@@ -34,6 +34,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Tests for {@link GsonAutoConfiguration}. * Tests for {@link GsonAutoConfiguration}.
* *
...@@ -246,7 +247,6 @@ public class GsonAutoConfigurationTests { ...@@ -246,7 +247,6 @@ public class GsonAutoConfigurationTests {
public class DataObject { public class DataObject {
@SuppressWarnings("unused")
public static final String STATIC_DATA = "bye"; public static final String STATIC_DATA = "bye";
@SuppressWarnings("unused") @SuppressWarnings("unused")
...@@ -255,14 +255,16 @@ public class GsonAutoConfigurationTests { ...@@ -255,14 +255,16 @@ public class GsonAutoConfigurationTests {
public void setData(Long data) { public void setData(Long data) {
this.data = data; this.data = data;
} }
} }
public class WrapperObject { public class WrapperObject {
@SuppressWarnings("unused") @SuppressWarnings("unused")
class NestedObject { class NestedObject {
@SuppressWarnings("unused")
private String data = "nested"; private String data = "nested";
} }
} }
......
...@@ -340,18 +340,18 @@ content into your application. Rather, pick only the properties that you need. ...@@ -340,18 +340,18 @@ content into your application. Rather, pick only the properties that you need.
spring.jackson.serialization.*= # Jackson on/off features that affect the way Java objects are serialized. spring.jackson.serialization.*= # Jackson on/off features that affect the way Java objects are serialized.
spring.jackson.time-zone= # Time zone used when formatting dates. For instance, "America/Los_Angeles" or "GMT+10". spring.jackson.time-zone= # Time zone used when formatting dates. For instance, "America/Los_Angeles" or "GMT+10".
# GSON ({sc-spring-boot-autoconfigure}/gson/GsonProperties.{sc-ext}[GsonProperties]) # GSON ({sc-spring-boot-autoconfigure}/gson/GsonProperties.{sc-ext}[GsonProperties])
spring.gson.date-format= # Configures Gson to serialize Date objects according to the pattern provided. spring.gson.date-format= # The format to use when serializing Date objects.
spring.gson.disable-html-escaping=false # By default, Gson escapes HTML characters such as < > etc. Use this option to configure Gson to pass-through HTML characters as is. spring.gson.disable-html-escaping= # Whether to disable the escaping of HTML characters such as '<' '>' etc.
spring.gson.disable-inner-class-serialization=false # Configures Gson to exclude inner classes during serialization. spring.gson.disable-inner-class-serialization= # Whether to exclude inner classes during serialization.
spring.gson.enable-complex-map-key-serialization=false # Enabling this feature will only change the serialized form if the map key is a complex type (i.e. non-primitive) in its serialized JSON form spring.gson.enable-complex-map-key-serialization= # Whether to enabled serialization of complex map keys (i.e. non-primitives).
spring.gson.exclude-fields-without-expose-annotation=false # Configures Gson to exclude all fields from consideration for serialization or deserialization that do not have the Expose annotation. spring.gson.exclude-fields-without-expose-annotation= # Whether to exclude all fields from consideration for serialization or deserialization that do not have the "Expose" annotation.
spring.gson.field-naming-policy= # Configures Gson to apply a specific naming policy to an object's field during serialization and deserialization. spring.gson.field-naming-policy= # The naming policy that should be applied to an object's field during serialization and deserialization.
spring.gson.generate-non-executable-json=false # Makes the output JSON non-executable in Javascript by prefixing the generated JSON with some special text. spring.gson.generate-non-executable-json= # Whether to generate non executable JSON by prefixing the output with some special text.
spring.gson.lenient=false # By default, Gson is strict and only accepts JSON as specified by RFC 4627. This option makes the parser liberal in what it accepts. spring.gson.lenient= # Whether to be lenient about parsing JSON that doesn't conform to RFC 4627.
spring.gson.long-serialization-policy= # Configures Gson to apply a specific serialization policy for Long and long objects. spring.gson.long-serialization-policy= # Serialization policy for Long and long types.
spring.gson.pretty-printing=false # Configures Gson to output Json that fits in a page for pretty printing. This option only affects Json serialization. spring.gson.pretty-printing= # Whether to output serialized JSON that fits in a page for pretty printing.
spring.gson.serialize-nulls=false # Configure Gson to serialize null fields. spring.gson.serialize-nulls= # Whether to to serialize null fields.
# JERSEY ({sc-spring-boot-autoconfigure}/jersey/JerseyProperties.{sc-ext}[JerseyProperties]) # JERSEY ({sc-spring-boot-autoconfigure}/jersey/JerseyProperties.{sc-ext}[JerseyProperties])
spring.jersey.application-path= # Path that serves as the base URI for the application. If specified, overrides the value of "@ApplicationPath". spring.jersey.application-path= # Path that serves as the base URI for the application. If specified, overrides the value of "@ApplicationPath".
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment