From eb381d642a00a9e36d70cd90fac4fca809106a25 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 12 Nov 2025 18:41:26 +0000 Subject: [PATCH] Provide default values for Kotlinx Serialization JSON properties Closes gh-48097 --- ...inxSerializationJsonAutoConfiguration.java | 25 ++-- .../KotlinxSerializationJsonProperties.java | 109 +++++++++--------- ...tlinxSerializationJsonPropertiesTests.java | 99 ++++++++++++++++ 3 files changed, 165 insertions(+), 68 deletions(-) create mode 100644 module/spring-boot-kotlinx-serialization-json/src/test/java/org/springframework/boot/kotlinx/serialization/json/autoconfigure/KotlinxSerializationJsonPropertiesTests.java diff --git a/module/spring-boot-kotlinx-serialization-json/src/main/java/org/springframework/boot/kotlinx/serialization/json/autoconfigure/KotlinxSerializationJsonAutoConfiguration.java b/module/spring-boot-kotlinx-serialization-json/src/main/java/org/springframework/boot/kotlinx/serialization/json/autoconfigure/KotlinxSerializationJsonAutoConfiguration.java index f8580556aba..7350b29458c 100644 --- a/module/spring-boot-kotlinx-serialization-json/src/main/java/org/springframework/boot/kotlinx/serialization/json/autoconfigure/KotlinxSerializationJsonAutoConfiguration.java +++ b/module/spring-boot-kotlinx-serialization-json/src/main/java/org/springframework/boot/kotlinx/serialization/json/autoconfigure/KotlinxSerializationJsonAutoConfiguration.java @@ -81,21 +81,20 @@ public final class KotlinxSerializationJsonAutoConfiguration { KotlinxSerializationJsonProperties properties = this.properties; PropertyMapper map = PropertyMapper.get(); map.from(properties::getNamingStrategy).to(setNamingStrategy(jsonBuilder)); - map.from(properties::getPrettyPrint).to(jsonBuilder::setPrettyPrint); - map.from(properties::getLenient).to(jsonBuilder::setLenient); - map.from(properties::getIgnoreUnknownKeys).to(jsonBuilder::setIgnoreUnknownKeys); - map.from(properties::getEncodeDefaults).to(jsonBuilder::setEncodeDefaults); - map.from(properties::getExplicitNulls).to(jsonBuilder::setExplicitNulls); - map.from(properties::getCoerceInputValues).to(jsonBuilder::setCoerceInputValues); - map.from(properties::getAllowStructuredMapKeys).to(jsonBuilder::setAllowStructuredMapKeys); - map.from(properties::getAllowSpecialFloatingPointValues) - .to(jsonBuilder::setAllowSpecialFloatingPointValues); + map.from(properties::isPrettyPrint).to(jsonBuilder::setPrettyPrint); + map.from(properties::isLenient).to(jsonBuilder::setLenient); + map.from(properties::isIgnoreUnknownKeys).to(jsonBuilder::setIgnoreUnknownKeys); + map.from(properties::isEncodeDefaults).to(jsonBuilder::setEncodeDefaults); + map.from(properties::isExplicitNulls).to(jsonBuilder::setExplicitNulls); + map.from(properties::isCoerceInputValues).to(jsonBuilder::setCoerceInputValues); + map.from(properties::isAllowStructuredMapKeys).to(jsonBuilder::setAllowStructuredMapKeys); + map.from(properties::isAllowSpecialFloatingPointValues).to(jsonBuilder::setAllowSpecialFloatingPointValues); map.from(properties::getClassDiscriminator).to(jsonBuilder::setClassDiscriminator); map.from(properties::getClassDiscriminatorMode).to(jsonBuilder::setClassDiscriminatorMode); - map.from(properties::getDecodeEnumsCaseInsensitive).to(jsonBuilder::setDecodeEnumsCaseInsensitive); - map.from(properties::getUseAlternativeNames).to(jsonBuilder::setUseAlternativeNames); - map.from(properties::getAllowTrailingComma).to(jsonBuilder::setAllowTrailingComma); - map.from(properties::getAllowComments).to(jsonBuilder::setAllowComments); + map.from(properties::isDecodeEnumsCaseInsensitive).to(jsonBuilder::setDecodeEnumsCaseInsensitive); + map.from(properties::isUseAlternativeNames).to(jsonBuilder::setUseAlternativeNames); + map.from(properties::isAllowTrailingComma).to(jsonBuilder::setAllowTrailingComma); + map.from(properties::isAllowComments).to(jsonBuilder::setAllowComments); } private Consumer setNamingStrategy(JsonBuilder builder) { diff --git a/module/spring-boot-kotlinx-serialization-json/src/main/java/org/springframework/boot/kotlinx/serialization/json/autoconfigure/KotlinxSerializationJsonProperties.java b/module/spring-boot-kotlinx-serialization-json/src/main/java/org/springframework/boot/kotlinx/serialization/json/autoconfigure/KotlinxSerializationJsonProperties.java index ec69dc7e8ab..f200b2442c0 100644 --- a/module/spring-boot-kotlinx-serialization-json/src/main/java/org/springframework/boot/kotlinx/serialization/json/autoconfigure/KotlinxSerializationJsonProperties.java +++ b/module/spring-boot-kotlinx-serialization-json/src/main/java/org/springframework/boot/kotlinx/serialization/json/autoconfigure/KotlinxSerializationJsonProperties.java @@ -38,83 +38,82 @@ public class KotlinxSerializationJsonProperties { private @Nullable JsonNamingStrategy namingStrategy; /** - * Whether resulting JSON should be pretty-printed: formatted and optimized for human - * readability. + * Whether resulting JSON should be pretty-printed. */ - private @Nullable Boolean prettyPrint; + private boolean prettyPrint; /** - * Enable lenient mode that removes JSON specification restriction (RFC-4627) and - * makes parser more liberal to the malformed input. + * Whether parser should operate in lenient mode, removing the JSON specification + * restriction (RFC-4627) and being more liberal to malformed input. */ - private @Nullable Boolean lenient; + private boolean lenient; /** * Whether encounters of unknown properties in the input JSON should be ignored * instead of throwing SerializationException. */ - private @Nullable Boolean ignoreUnknownKeys; + private boolean ignoreUnknownKeys; /** * Whether default values of Kotlin properties should be encoded. */ - private @Nullable Boolean encodeDefaults; + private boolean encodeDefaults; /** * Whether null values should be encoded for nullable properties and must be present * in JSON object during decoding. */ - private @Nullable Boolean explicitNulls; + private boolean explicitNulls = true; /** - * Enable coercing incorrect JSON values. + * Whether to coerce incorrect JSON values. */ - private @Nullable Boolean coerceInputValues; + private boolean coerceInputValues; /** - * Enable structured objects to be serialized as map keys by changing serialized form - * of the map from JSON object (key-value pairs) to flat array like [k1, v1, k2, v2]. + * Whether to allow structured objects to be serialized as map keys by changing the + * serialized form of the map from JSON object (key-value pairs) to flat array like + * [k1, v1, k2, v2]. */ - private @Nullable Boolean allowStructuredMapKeys; + private boolean allowStructuredMapKeys; /** - * Whether to remove JSON specification restriction on special floating-point values - * such as 'NaN' and 'Infinity' and enable their serialization and deserialization as - * float literals without quotes. + * Whether to remove the JSON specification restriction on special floating-point + * values such as 'NaN' and 'Infinity' and allow their serialization and + * deserialization as float literals without quotes. */ - private @Nullable Boolean allowSpecialFloatingPointValues; + private boolean allowSpecialFloatingPointValues; /** * Name of the class descriptor property for polymorphic serialization. */ - private @Nullable String classDiscriminator; + private String classDiscriminator = "type"; /** * Defines which classes and objects should have class discriminator added to the * output. */ - private @Nullable ClassDiscriminatorMode classDiscriminatorMode; + private ClassDiscriminatorMode classDiscriminatorMode = ClassDiscriminatorMode.POLYMORPHIC; /** - * Enable decoding enum values in a case-insensitive manner. + * Whether enum values are decoded in a case-insensitive manner. */ - private @Nullable Boolean decodeEnumsCaseInsensitive; + private boolean decodeEnumsCaseInsensitive; /** * Whether Json instance makes use of JsonNames annotation. */ - private @Nullable Boolean useAlternativeNames; + private boolean useAlternativeNames = true; /** - * Whether to allow parser to accept trailing (ending) commas in JSON objects and - * arrays. + * Whether to allow parser to accept trailing commas in JSON objects and arrays. */ - private @Nullable Boolean allowTrailingComma; + private boolean allowTrailingComma; /** * Whether to allow parser to accept C/Java-style comments in JSON input. */ - private @Nullable Boolean allowComments; + private boolean allowComments; public @Nullable JsonNamingStrategy getNamingStrategy() { return this.namingStrategy; @@ -124,115 +123,115 @@ public class KotlinxSerializationJsonProperties { this.namingStrategy = namingStrategy; } - public @Nullable Boolean getPrettyPrint() { + public boolean isPrettyPrint() { return this.prettyPrint; } - public void setPrettyPrint(@Nullable Boolean prettyPrint) { + public void setPrettyPrint(boolean prettyPrint) { this.prettyPrint = prettyPrint; } - public @Nullable Boolean getLenient() { + public boolean isLenient() { return this.lenient; } - public void setLenient(@Nullable Boolean lenient) { + public void setLenient(boolean lenient) { this.lenient = lenient; } - public @Nullable Boolean getIgnoreUnknownKeys() { + public boolean isIgnoreUnknownKeys() { return this.ignoreUnknownKeys; } - public void setIgnoreUnknownKeys(@Nullable Boolean ignoreUnknownKeys) { + public void setIgnoreUnknownKeys(boolean ignoreUnknownKeys) { this.ignoreUnknownKeys = ignoreUnknownKeys; } - public @Nullable Boolean getEncodeDefaults() { + public boolean isEncodeDefaults() { return this.encodeDefaults; } - public void setEncodeDefaults(@Nullable Boolean encodeDefaults) { + public void setEncodeDefaults(boolean encodeDefaults) { this.encodeDefaults = encodeDefaults; } - public @Nullable Boolean getExplicitNulls() { + public boolean isExplicitNulls() { return this.explicitNulls; } - public void setExplicitNulls(@Nullable Boolean explicitNulls) { + public void setExplicitNulls(boolean explicitNulls) { this.explicitNulls = explicitNulls; } - public @Nullable Boolean getCoerceInputValues() { + public boolean isCoerceInputValues() { return this.coerceInputValues; } - public void setCoerceInputValues(@Nullable Boolean coerceInputValues) { + public void setCoerceInputValues(boolean coerceInputValues) { this.coerceInputValues = coerceInputValues; } - public @Nullable Boolean getAllowStructuredMapKeys() { + public boolean isAllowStructuredMapKeys() { return this.allowStructuredMapKeys; } - public void setAllowStructuredMapKeys(@Nullable Boolean allowStructuredMapKeys) { + public void setAllowStructuredMapKeys(boolean allowStructuredMapKeys) { this.allowStructuredMapKeys = allowStructuredMapKeys; } - public @Nullable Boolean getAllowSpecialFloatingPointValues() { + public boolean isAllowSpecialFloatingPointValues() { return this.allowSpecialFloatingPointValues; } - public void setAllowSpecialFloatingPointValues(@Nullable Boolean allowSpecialFloatingPointValues) { + public void setAllowSpecialFloatingPointValues(boolean allowSpecialFloatingPointValues) { this.allowSpecialFloatingPointValues = allowSpecialFloatingPointValues; } - public @Nullable String getClassDiscriminator() { + public String getClassDiscriminator() { return this.classDiscriminator; } - public void setClassDiscriminator(@Nullable String classDiscriminator) { + public void setClassDiscriminator(String classDiscriminator) { this.classDiscriminator = classDiscriminator; } - public @Nullable ClassDiscriminatorMode getClassDiscriminatorMode() { + public ClassDiscriminatorMode getClassDiscriminatorMode() { return this.classDiscriminatorMode; } - public void setClassDiscriminatorMode(@Nullable ClassDiscriminatorMode classDiscriminatorMode) { + public void setClassDiscriminatorMode(ClassDiscriminatorMode classDiscriminatorMode) { this.classDiscriminatorMode = classDiscriminatorMode; } - public @Nullable Boolean getDecodeEnumsCaseInsensitive() { + public boolean isDecodeEnumsCaseInsensitive() { return this.decodeEnumsCaseInsensitive; } - public void setDecodeEnumsCaseInsensitive(@Nullable Boolean decodeEnumsCaseInsensitive) { + public void setDecodeEnumsCaseInsensitive(boolean decodeEnumsCaseInsensitive) { this.decodeEnumsCaseInsensitive = decodeEnumsCaseInsensitive; } - public @Nullable Boolean getUseAlternativeNames() { + public boolean isUseAlternativeNames() { return this.useAlternativeNames; } - public void setUseAlternativeNames(@Nullable Boolean useAlternativeNames) { + public void setUseAlternativeNames(boolean useAlternativeNames) { this.useAlternativeNames = useAlternativeNames; } - public @Nullable Boolean getAllowTrailingComma() { + public boolean isAllowTrailingComma() { return this.allowTrailingComma; } - public void setAllowTrailingComma(@Nullable Boolean allowTrailingComma) { + public void setAllowTrailingComma(boolean allowTrailingComma) { this.allowTrailingComma = allowTrailingComma; } - public @Nullable Boolean getAllowComments() { + public boolean isAllowComments() { return this.allowComments; } - public void setAllowComments(@Nullable Boolean allowComments) { + public void setAllowComments(boolean allowComments) { this.allowComments = allowComments; } diff --git a/module/spring-boot-kotlinx-serialization-json/src/test/java/org/springframework/boot/kotlinx/serialization/json/autoconfigure/KotlinxSerializationJsonPropertiesTests.java b/module/spring-boot-kotlinx-serialization-json/src/test/java/org/springframework/boot/kotlinx/serialization/json/autoconfigure/KotlinxSerializationJsonPropertiesTests.java new file mode 100644 index 00000000000..90f29862662 --- /dev/null +++ b/module/spring-boot-kotlinx-serialization-json/src/test/java/org/springframework/boot/kotlinx/serialization/json/autoconfigure/KotlinxSerializationJsonPropertiesTests.java @@ -0,0 +1,99 @@ +/* + * Copyright 2012-present 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.kotlinx.serialization.json.autoconfigure; + +import kotlinx.serialization.json.Json; +import kotlinx.serialization.json.JsonBuilder; +import org.jspecify.annotations.Nullable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link KotlinxSerializationJsonProperties}. + * + * @author Andy Wilkinson + */ +class KotlinxSerializationJsonPropertiesTests { + + @ParameterizedTest + @EnumSource + void defaultsAreAligned(JsonBuilderSettings settings) { + JsonBuilder jsonBuilder = new JsonBuilder(Json.Default); + KotlinxSerializationJsonProperties properties = new KotlinxSerializationJsonProperties(); + assertThat(settings.propertyGetter.get(properties)).isEqualTo(settings.jsonBuilderGetter.get(jsonBuilder)); + } + + private enum JsonBuilderSettings { + + ALLOW_COMMENTS(KotlinxSerializationJsonProperties::isAllowComments, JsonBuilder::getAllowComments), + + ALLOW_SPECIAL_FLOATING_POINT_VALUES(KotlinxSerializationJsonProperties::isAllowSpecialFloatingPointValues, + JsonBuilder::getAllowSpecialFloatingPointValues), + + ALLOW_STRUCTURED_MAP_KEYS(KotlinxSerializationJsonProperties::isAllowStructuredMapKeys, + JsonBuilder::getAllowStructuredMapKeys), + + ALLOW_TRAILING_COMMA(KotlinxSerializationJsonProperties::isAllowTrailingComma, + JsonBuilder::getAllowTrailingComma), + + CLASS_DISCRIMINATOR(KotlinxSerializationJsonProperties::getClassDiscriminator, + JsonBuilder::getClassDiscriminator), + + CLASS_DISCRIMINATOR_MODE(KotlinxSerializationJsonProperties::getClassDiscriminatorMode, + JsonBuilder::getClassDiscriminatorMode), + + COERCE_INPUT_VALUES(KotlinxSerializationJsonProperties::isCoerceInputValues, JsonBuilder::getCoerceInputValues), + + DECODE_ENUMS_CASE_INSENSITIVE(KotlinxSerializationJsonProperties::isDecodeEnumsCaseInsensitive, + JsonBuilder::getDecodeEnumsCaseInsensitive), + + ENCODE_DEFAULTS(KotlinxSerializationJsonProperties::isEncodeDefaults, JsonBuilder::getEncodeDefaults), + + EXPLICIT_NULLS(KotlinxSerializationJsonProperties::isExplicitNulls, JsonBuilder::getExplicitNulls), + + IGNORE_UNKNOWN_KEYS(KotlinxSerializationJsonProperties::isIgnoreUnknownKeys, JsonBuilder::getIgnoreUnknownKeys), + + LENIENT(KotlinxSerializationJsonProperties::isLenient, JsonBuilder::isLenient), + + NAMING_STRATEGY(KotlinxSerializationJsonProperties::getNamingStrategy, JsonBuilder::getNamingStrategy), + + PRETTY_PRINT(KotlinxSerializationJsonProperties::isPrettyPrint, JsonBuilder::getPrettyPrint), + + USE_ALTERNATIVE_NAMES(KotlinxSerializationJsonProperties::isUseAlternativeNames, + JsonBuilder::getUseAlternativeNames); + + private final Accessor propertyGetter; + + private final Accessor jsonBuilderGetter; + + JsonBuilderSettings(Accessor propertyGetter, + Accessor jsonBuilderGetter) { + this.propertyGetter = propertyGetter; + this.jsonBuilderGetter = jsonBuilderGetter; + } + + private interface Accessor { + + @Nullable P get(S source); + + } + + } + +}