From 286a3bb10aa6884c4c93245d1c928ec7d3db69fb Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 8 Jan 2018 22:39:45 -0800 Subject: [PATCH] Polish GSON customization support Closes gh-11498 --- .../gson/GsonAutoConfiguration.java | 159 ++++++------------ .../gson/GsonBuilderCustomizer.java | 2 +- .../autoconfigure/gson/GsonProperties.java | 86 +++++----- .../gson/GsonAutoConfigurationTests.java | 8 +- .../appendix-application-properties.adoc | 24 +-- 5 files changed, 109 insertions(+), 170 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/gson/GsonAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/gson/GsonAutoConfiguration.java index ab51bf9799..c3e2e56472 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/gson/GsonAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/gson/GsonAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * 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"); * you may not use this file except in compliance with the License. @@ -18,18 +18,16 @@ package org.springframework.boot.autoconfigure.gson; import java.util.List; -import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import com.google.gson.LongSerializationPolicy; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 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.Configuration; -import org.springframework.context.annotation.Primary; import org.springframework.core.Ordered; /** @@ -41,120 +39,65 @@ import org.springframework.core.Ordered; */ @Configuration @ConditionalOnClass(Gson.class) +@EnableConfigurationProperties(GsonProperties.class) public class GsonAutoConfiguration { - @Configuration - static class GsonConfiguration { - - @Bean - @Primary - @ConditionalOnMissingBean(Gson.class) - public Gson gson(GsonBuilder gsonBuilder) { - return gsonBuilder.create(); - } + @Bean + public GsonBuilder gsonBuilder(List customizers) { + GsonBuilder builder = new GsonBuilder(); + customizers.forEach(c -> c.customize(builder)); + return builder; } - @Configuration - static class GsonBuilderConfiguration { - - @Bean - public GsonBuilder gsonBuilder(List customizers) { - final GsonBuilder gsonBuilder = new GsonBuilder(); - customizers.forEach(c -> c.customize(gsonBuilder)); - return gsonBuilder; - } - + @Bean + @ConditionalOnMissingBean(Gson.class) + public Gson gson(GsonBuilder gsonBuilder) { + return gsonBuilder.create(); } - @Configuration - @EnableConfigurationProperties(GsonProperties.class) - static class GsonBuilderCustomizerConfiguration { + @Bean + public StandardGsonBuilderCustomizer standardGsonBuilderCustomizer( + 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; } - 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 - .getFieldNamingPolicy(); - if (fieldNamingPolicy != null) { - gsonBuilder.setFieldNamingPolicy(fieldNamingPolicy); - } - - boolean prettyPrinting = this.properties.isPrettyPrinting(); - if (prettyPrinting) { - gsonBuilder.setPrettyPrinting(); - } - - boolean isLenient = this.properties.isLenient(); - if (isLenient) { - gsonBuilder.setLenient(); - } - - boolean disableHtmlEscaping = this.properties.isDisableHtmlEscaping(); - if (disableHtmlEscaping) { - gsonBuilder.disableHtmlEscaping(); - } - - String dateFormat = this.properties.getDateFormat(); - if (dateFormat != null) { - gsonBuilder.setDateFormat(dateFormat); - } - } + @Override + public int getOrder() { + return 0; } + + @Override + public void customize(GsonBuilder builder) { + 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); + } + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/gson/GsonBuilderCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/gson/GsonBuilderCustomizer.java index cf9054f3e6..cecf888ed4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/gson/GsonBuilderCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/gson/GsonBuilderCustomizer.java @@ -1,5 +1,5 @@ /* - * 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"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/gson/GsonProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/gson/GsonProperties.java index 2ca8f96d70..d918d5c52f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/gson/GsonProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/gson/GsonProperties.java @@ -1,5 +1,5 @@ /* - * 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"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ package org.springframework.boot.autoconfigure.gson; import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.LongSerializationPolicy; -import com.google.gson.annotations.Expose; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -33,109 +32,103 @@ import org.springframework.boot.context.properties.ConfigurationProperties; public class GsonProperties { /** - * Makes the output JSON non-executable in Javascript by prefixing the generated JSON - * with some special text. + * Whether to generate non executable JSON by prefixing the output with some special + * text. */ - private boolean generateNonExecutableJson; + private Boolean generateNonExecutableJson; /** - * Configures {@link Gson} to exclude all fields from consideration for serialization - * or deserialization that do not have the {@link Expose} annotation. + * Whether to exclude all fields from consideration for serialization or + * 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 - * complex type (i.e. non-primitive) in its serialized JSON form + * Whether to enabled serialization of complex map keys (i.e. non-primitives). */ - 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 - * objects. + * Serialization policy for Long and long types. */ private LongSerializationPolicy longSerializationPolicy; /** - * Configures {@link Gson} to apply a specific naming policy to an object's field - * during serialization and deserialization. + * The naming policy that should be applied to an object's field during serialization + * and deserialization. */ private FieldNamingPolicy fieldNamingPolicy; /** - * Configures {@link Gson} to output Json that fits in a page for pretty printing. - * This option only affects Json serialization. + * Whether to output serialized JSON that fits in a page for pretty printing. */ - private boolean prettyPrinting; + private Boolean prettyPrinting; /** - * By default, {@link Gson} is strict and only accepts JSON as specified by RFC 4627. - * This option makes the parser liberal in what it accepts. + * Whether to be lenient about parsing JSON that doesn't conform to RFC 4627. */ - private boolean lenient; + private Boolean lenient; /** - * By default, {@link Gson} escapes HTML characters such as < > etc. Use this option - * to configure Gson to pass-through HTML characters as is. + * Whether to disable the escaping of HTML characters such as '<' '>' etc. */ - private boolean disableHtmlEscaping; + private Boolean disableHtmlEscaping; /** - * Configures {@link Gson} to serialize Date objects according to the pattern - * provided. + * The format to use when serializing Date objects. */ private String dateFormat; - public boolean isGenerateNonExecutableJson() { + public Boolean getGenerateNonExecutableJson() { return this.generateNonExecutableJson; } - public void setGenerateNonExecutableJson(boolean generateNonExecutableJson) { + public void setGenerateNonExecutableJson(Boolean generateNonExecutableJson) { this.generateNonExecutableJson = generateNonExecutableJson; } - public boolean isExcludeFieldsWithoutExposeAnnotation() { + public Boolean getExcludeFieldsWithoutExposeAnnotation() { return this.excludeFieldsWithoutExposeAnnotation; } public void setExcludeFieldsWithoutExposeAnnotation( - boolean excludeFieldsWithoutExposeAnnotation) { + Boolean excludeFieldsWithoutExposeAnnotation) { this.excludeFieldsWithoutExposeAnnotation = excludeFieldsWithoutExposeAnnotation; } - public boolean isSerializeNulls() { + public Boolean getSerializeNulls() { return this.serializeNulls; } - public void setSerializeNulls(boolean serializeNulls) { + public void setSerializeNulls(Boolean serializeNulls) { this.serializeNulls = serializeNulls; } - public boolean isEnableComplexMapKeySerialization() { + public Boolean getEnableComplexMapKeySerialization() { return this.enableComplexMapKeySerialization; } public void setEnableComplexMapKeySerialization( - boolean enableComplexMapKeySerialization) { + Boolean enableComplexMapKeySerialization) { this.enableComplexMapKeySerialization = enableComplexMapKeySerialization; } - public boolean isDisableInnerClassSerialization() { + public Boolean getDisableInnerClassSerialization() { return this.disableInnerClassSerialization; } public void setDisableInnerClassSerialization( - boolean disableInnerClassSerialization) { + Boolean disableInnerClassSerialization) { this.disableInnerClassSerialization = disableInnerClassSerialization; } @@ -156,27 +149,27 @@ public class GsonProperties { this.fieldNamingPolicy = fieldNamingPolicy; } - public boolean isPrettyPrinting() { + public Boolean getPrettyPrinting() { return this.prettyPrinting; } - public void setPrettyPrinting(boolean prettyPrinting) { + public void setPrettyPrinting(Boolean prettyPrinting) { this.prettyPrinting = prettyPrinting; } - public boolean isLenient() { + public Boolean getLenient() { return this.lenient; } - public void setLenient(boolean lenient) { + public void setLenient(Boolean lenient) { this.lenient = lenient; } - public boolean isDisableHtmlEscaping() { + public Boolean getDisableHtmlEscaping() { return this.disableHtmlEscaping; } - public void setDisableHtmlEscaping(boolean disableHtmlEscaping) { + public void setDisableHtmlEscaping(Boolean disableHtmlEscaping) { this.disableHtmlEscaping = disableHtmlEscaping; } @@ -187,4 +180,5 @@ public class GsonProperties { public void setDateFormat(String dateFormat) { this.dateFormat = dateFormat; } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/gson/GsonAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/gson/GsonAutoConfigurationTests.java index f077a0c73f..5e2cc2cfc3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/gson/GsonAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/gson/GsonAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * 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"); * you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import static org.assertj.core.api.Assertions.assertThat; + /** * Tests for {@link GsonAutoConfiguration}. * @@ -246,7 +247,6 @@ public class GsonAutoConfigurationTests { public class DataObject { - @SuppressWarnings("unused") public static final String STATIC_DATA = "bye"; @SuppressWarnings("unused") @@ -255,14 +255,16 @@ public class GsonAutoConfigurationTests { public void setData(Long data) { this.data = data; } + } public class WrapperObject { @SuppressWarnings("unused") class NestedObject { - @SuppressWarnings("unused") + private String data = "nested"; + } } diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 9a9cbed9fe..87a0a09b09 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -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.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]) - spring.gson.date-format= # Configures Gson to serialize Date objects according to the pattern provided. - 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-inner-class-serialization=false # Configures Gson 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.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.field-naming-policy= # Configures Gson to apply a specific naming policy 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.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.long-serialization-policy= # Configures Gson to apply a specific serialization policy for Long and long objects. - 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.serialize-nulls=false # Configure Gson to serialize null fields. + # GSON ({sc-spring-boot-autoconfigure}/gson/GsonProperties.{sc-ext}[GsonProperties]) + spring.gson.date-format= # The format to use when serializing Date objects. + spring.gson.disable-html-escaping= # Whether to disable the escaping of HTML characters such as '<' '>' etc. + spring.gson.disable-inner-class-serialization= # Whether to exclude inner classes during serialization. + 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= # Whether to exclude all fields from consideration for serialization or deserialization that do not have the "Expose" annotation. + 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= # Whether to generate non executable JSON by prefixing the output with some special text. + spring.gson.lenient= # Whether to be lenient about parsing JSON that doesn't conform to RFC 4627. + spring.gson.long-serialization-policy= # Serialization policy for Long and long types. + spring.gson.pretty-printing= # Whether to output serialized JSON that fits in a page for pretty printing. + spring.gson.serialize-nulls= # Whether to to serialize null fields. # 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".