Skip to content

Commit 2680329

Browse files
committed
Add configuration property for preferred json mapper in webflux
Signed-off-by: Vasily Pelikh <[email protected]>
1 parent 46022d2 commit 2680329

File tree

5 files changed

+297
-75
lines changed

5 files changed

+297
-75
lines changed

module/spring-boot-http-codec/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ dependencies {
3030

3131
optional(project(":core:spring-boot-autoconfigure"))
3232
optional(project(":core:spring-boot-test"))
33+
optional(project(":module:spring-boot-gson"))
3334
optional(project(":module:spring-boot-jackson"))
35+
optional(project(":module:spring-boot-kotlin-serialization"))
3436
optional("org.springframework:spring-webflux")
3537

3638
testImplementation(project(":core:spring-boot-test"))

module/spring-boot-http-codec/src/main/java/org/springframework/boot/http/codec/autoconfigure/CodecsAutoConfiguration.java

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,17 @@
1616

1717
package org.springframework.boot.http.codec.autoconfigure;
1818

19+
import com.google.gson.Gson;
20+
import kotlinx.serialization.Serializable;
21+
import kotlinx.serialization.json.Json;
1922
import org.jspecify.annotations.Nullable;
20-
import tools.jackson.databind.ObjectMapper;
2123
import tools.jackson.databind.json.JsonMapper;
2224

2325
import org.springframework.boot.autoconfigure.AutoConfiguration;
2426
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2527
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2628
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
29+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
2730
import org.springframework.boot.context.properties.EnableConfigurationProperties;
2831
import org.springframework.boot.context.properties.PropertyMapper;
2932
import org.springframework.boot.http.codec.CodecCustomizer;
@@ -32,8 +35,13 @@
3235
import org.springframework.core.Ordered;
3336
import org.springframework.core.annotation.Order;
3437
import org.springframework.http.codec.CodecConfigurer;
38+
import org.springframework.http.codec.CodecConfigurer.CustomCodecs;
39+
import org.springframework.http.codec.json.GsonDecoder;
40+
import org.springframework.http.codec.json.GsonEncoder;
3541
import org.springframework.http.codec.json.JacksonJsonDecoder;
3642
import org.springframework.http.codec.json.JacksonJsonEncoder;
43+
import org.springframework.http.codec.json.KotlinSerializationJsonDecoder;
44+
import org.springframework.http.codec.json.KotlinSerializationJsonEncoder;
3745
import org.springframework.util.unit.DataSize;
3846
import org.springframework.web.reactive.function.client.WebClient;
3947

@@ -43,19 +51,25 @@
4351
* {@link org.springframework.core.codec.Decoder Decoders}.
4452
*
4553
* @author Brian Clozel
54+
* @author Vasily Pelikh
4655
* @since 2.0.0
4756
*/
48-
@AutoConfiguration(afterName = "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration")
57+
@AutoConfiguration(afterName = { "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration",
58+
"org.springframework.boot.gson.autoconfigure.GsonAutoConfiguration",
59+
"org.springframework.boot.kotlin.serialization.autoconfigure.KotlinSerializationAutoConfiguration" })
4960
@ConditionalOnClass({ CodecConfigurer.class, WebClient.class })
5061
public final class CodecsAutoConfiguration {
5162

63+
private static final String PREFERRED_MAPPER_PROPERTY = "spring.http.codecs.preferred-json-mapper";
64+
5265
@Configuration(proxyBeanMethods = false)
53-
@ConditionalOnClass(ObjectMapper.class)
66+
@ConditionalOnClass(JsonMapper.class)
67+
@ConditionalOnBean(JsonMapper.class)
68+
@ConditionalOnProperty(name = PREFERRED_MAPPER_PROPERTY, havingValue = "jackson", matchIfMissing = true)
5469
static class JacksonJsonCodecConfiguration {
5570

5671
@Bean
5772
@Order(0)
58-
@ConditionalOnBean(JsonMapper.class)
5973
CodecCustomizer jacksonCodecCustomizer(JsonMapper jsonMapper) {
6074
return (configurer) -> {
6175
CodecConfigurer.DefaultCodecs defaults = configurer.defaultCodecs();
@@ -66,6 +80,41 @@ CodecCustomizer jacksonCodecCustomizer(JsonMapper jsonMapper) {
6680

6781
}
6882

83+
@Configuration(proxyBeanMethods = false)
84+
@ConditionalOnClass(Gson.class)
85+
@ConditionalOnBean(Gson.class)
86+
@ConditionalOnProperty(name = PREFERRED_MAPPER_PROPERTY, havingValue = "gson")
87+
static class GsonJsonCodecConfiguration {
88+
89+
@Bean
90+
CodecCustomizer gsonCodecCustomizer(Gson gson) {
91+
return (configurer) -> {
92+
CustomCodecs customCodecs = configurer.customCodecs();
93+
customCodecs.registerWithDefaultConfig(new GsonDecoder(gson));
94+
customCodecs.registerWithDefaultConfig(new GsonEncoder(gson));
95+
};
96+
}
97+
98+
}
99+
100+
@Configuration(proxyBeanMethods = false)
101+
@ConditionalOnClass({ Serializable.class, Json.class })
102+
@ConditionalOnBean(Json.class)
103+
@ConditionalOnProperty(name = PREFERRED_MAPPER_PROPERTY, havingValue = "kotlin-serialization")
104+
static class KotlinSerializationJsonCodecConfiguration {
105+
106+
@Bean
107+
@Order(-10) // configured ahead of JSON mappers
108+
CodecCustomizer kotlinSerializationCodecCustomizer(Json json) {
109+
return (configurer) -> {
110+
CustomCodecs customCodecs = configurer.customCodecs();
111+
customCodecs.registerWithDefaultConfig(new KotlinSerializationJsonDecoder(json));
112+
customCodecs.registerWithDefaultConfig(new KotlinSerializationJsonEncoder(json));
113+
};
114+
}
115+
116+
}
117+
69118
@Configuration(proxyBeanMethods = false)
70119
@EnableConfigurationProperties(HttpCodecsProperties.class)
71120
static class DefaultCodecsConfiguration {
Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,31 @@
11
{
2-
"groups": [],
3-
"properties": []
2+
"properties": [
3+
{
4+
"name": "spring.http.codecs.preferred-json-mapper",
5+
"type": "java.lang.String",
6+
"defaultValue": "jackson",
7+
"description": "Preferred JSON mapper to use for JSON encoder and decoder. By default, auto-detected according to the environment. Supported values are 'jackson', 'gson' and 'kotlin-serialization'."
8+
}
9+
],
10+
"hints": [
11+
{
12+
"name": "spring.http.codecs.preferred-json-mapper",
13+
"values": [
14+
{
15+
"value": "jackson"
16+
},
17+
{
18+
"value": "gson"
19+
},
20+
{
21+
"value": "kotlin-serialization"
22+
}
23+
],
24+
"providers": [
25+
{
26+
"name": "any"
27+
}
28+
]
29+
}
30+
]
431
}

module/spring-boot-http-codec/src/test/java/org/springframework/boot/http/codec/autoconfigure/CodecsAutoConfigurationTests.java

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,12 @@
1616

1717
package org.springframework.boot.http.codec.autoconfigure;
1818

19-
import java.util.List;
20-
2119
import org.junit.jupiter.api.Test;
22-
import tools.jackson.databind.json.JsonMapper;
2320

2421
import org.springframework.boot.autoconfigure.AutoConfigurations;
2522
import org.springframework.boot.http.codec.CodecCustomizer;
2623
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
2724
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
28-
import org.springframework.context.annotation.Bean;
29-
import org.springframework.context.annotation.Configuration;
3025
import org.springframework.core.Ordered;
3126
import org.springframework.http.codec.CodecConfigurer;
3227
import org.springframework.http.codec.CodecConfigurer.DefaultCodecs;
@@ -71,27 +66,6 @@ void defaultCodecCustomizerBeanShouldHaveOrderZero() {
7166
.run((context) -> assertThat(context.getBean("defaultCodecCustomizer", Ordered.class).getOrder()).isZero());
7267
}
7368

74-
@Test
75-
void jacksonCodecCustomizerBacksOffWhenThereIsNoObjectMapper() {
76-
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean("jacksonCodecCustomizer"));
77-
}
78-
79-
@Test
80-
void jacksonCodecCustomizerIsAutoConfiguredWhenJsonMapperIsPresent() {
81-
this.contextRunner.withUserConfiguration(JsonMapperConfiguration.class)
82-
.run((context) -> assertThat(context).hasBean("jacksonCodecCustomizer"));
83-
}
84-
85-
@Test
86-
void userProvidedCustomizerCanOverrideJacksonCodecCustomizer() {
87-
this.contextRunner.withUserConfiguration(JsonMapperConfiguration.class, CodecCustomizerConfiguration.class)
88-
.run((context) -> {
89-
List<CodecCustomizer> codecCustomizers = context.getBean(CodecCustomizers.class).codecCustomizers;
90-
assertThat(codecCustomizers).hasSize(3);
91-
assertThat(codecCustomizers.get(2)).isInstanceOf(TestCodecCustomizer.class);
92-
});
93-
}
94-
9569
@Test
9670
void maxInMemorySizeEnforcedInDefaultCodecs() {
9771
this.contextRunner.withPropertyValues("spring.http.codecs.max-in-memory-size=1MB")
@@ -106,47 +80,4 @@ private DefaultCodecs defaultCodecs(AssertableApplicationContext context) {
10680
return configurer.defaultCodecs();
10781
}
10882

109-
@Configuration(proxyBeanMethods = false)
110-
static class JsonMapperConfiguration {
111-
112-
@Bean
113-
JsonMapper jsonMapper() {
114-
return new JsonMapper();
115-
}
116-
117-
}
118-
119-
@Configuration(proxyBeanMethods = false)
120-
static class CodecCustomizerConfiguration {
121-
122-
@Bean
123-
CodecCustomizer codecCustomizer() {
124-
return new TestCodecCustomizer();
125-
}
126-
127-
@Bean
128-
CodecCustomizers codecCustomizers(List<CodecCustomizer> customizers) {
129-
return new CodecCustomizers(customizers);
130-
}
131-
132-
}
133-
134-
private static final class TestCodecCustomizer implements CodecCustomizer {
135-
136-
@Override
137-
public void customize(CodecConfigurer configurer) {
138-
}
139-
140-
}
141-
142-
private static final class CodecCustomizers {
143-
144-
private final List<CodecCustomizer> codecCustomizers;
145-
146-
private CodecCustomizers(List<CodecCustomizer> codecCustomizers) {
147-
this.codecCustomizers = codecCustomizers;
148-
}
149-
150-
}
151-
15283
}

0 commit comments

Comments
 (0)