Skip to content

Commit 6ce4e53

Browse files
committed
Reinstate Jackson 2 compatibility
This commit reinstates Jackson 2 compatibility, as a fallback if Jackson 3 is not available. Closes gh-1236
1 parent 1627895 commit 6ce4e53

File tree

9 files changed

+131
-11
lines changed

9 files changed

+131
-11
lines changed

platform/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ javaPlatform {
88

99
dependencies {
1010
api(platform("org.springframework:spring-framework-bom:${springFrameworkVersion}"))
11+
api(platform("com.fasterxml.jackson:jackson-bom:2.18.4"))
1112
api(platform("tools.jackson:jackson-bom:3.0.0-rc4"))
1213
api(platform("io.projectreactor:reactor-bom:2025.0.0-M3"))
1314
api(platform("io.micrometer:micrometer-bom:1.15.0"))

spring-graphql-test/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ dependencies {
1515
compileOnly 'org.springframework:spring-messaging'
1616
compileOnly 'jakarta.servlet:jakarta.servlet-api'
1717
compileOnly 'tools.jackson.core:jackson-databind'
18+
compileOnly 'com.fasterxml.jackson.core:jackson-databind'
1819
compileOnly 'io.rsocket:rsocket-core'
1920
compileOnly 'io.rsocket:rsocket-transport-netty'
2021
compileOnly 'org.skyscreamer:jsonassert'
@@ -35,6 +36,7 @@ dependencies {
3536
testImplementation 'io.rsocket:rsocket-transport-local'
3637
testImplementation 'io.micrometer:context-propagation'
3738
testImplementation 'tools.jackson.core:jackson-databind'
39+
testImplementation 'com.fasterxml.jackson.core:jackson-databind'
3840

3941
testRuntimeOnly 'org.apache.logging.log4j:log4j-core'
4042
testRuntimeOnly 'org.apache.logging.log4j:log4j-slf4j-impl'

spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/AbstractGraphQlTesterBuilder.java

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.function.Predicate;
2525

2626
import com.jayway.jsonpath.Configuration;
27+
import com.jayway.jsonpath.spi.json.JsonProvider;
2728
import com.jayway.jsonpath.spi.mapper.MappingProvider;
2829
import org.jspecify.annotations.Nullable;
2930
import reactor.core.publisher.Flux;
@@ -61,6 +62,9 @@ public abstract class AbstractGraphQlTesterBuilder<B extends AbstractGraphQlTest
6162
private static final boolean jacksonPresent = ClassUtils.isPresent(
6263
"tools.jackson.databind.ObjectMapper", AbstractGraphQlClientBuilder.class.getClassLoader());
6364

65+
private static final boolean jackson2Present = ClassUtils.isPresent(
66+
"com.fasterxml.jackson.databind.ObjectMapper", AbstractGraphQlClientBuilder.class.getClassLoader());
67+
6468
private static final Duration DEFAULT_RESPONSE_DURATION = Duration.ofSeconds(5);
6569

6670

@@ -131,6 +135,9 @@ protected GraphQlTester buildGraphQlTester(GraphQlTransport transport) {
131135
if (jacksonPresent) {
132136
configureJsonPathConfig(JacksonConfigurer::configure);
133137
}
138+
else if (jackson2Present) {
139+
configureJsonPathConfig(Jackson2Configurer::configure);
140+
}
134141

135142
return new DefaultGraphQlTester(transport, this.errorFilter,
136143
this.jsonPathConfig, this.documentSource, this.responseTimeout);
@@ -192,9 +199,7 @@ private static void copyAttributes(Map<String, Object> map, GraphQlRequest reque
192199
};
193200
}
194201

195-
196-
private static final class JacksonConfigurer {
197-
202+
private abstract static class AbstractJacksonConfigurer {
198203
private static final Class<?> defaultJsonProviderType;
199204

200205
private static final Class<?> defaultMappingProviderType;
@@ -208,20 +213,37 @@ private static final class JacksonConfigurer {
208213
// GraphQlTransport returns ExecutionResult with JSON parsed to Map/List,
209214
// but we still need JsonProvider for matchesJson(String)
210215

211-
static Configuration configure(Configuration config) {
216+
static Configuration configure(Configuration config, JsonProvider jsonProvider, MappingProvider mappingProvider) {
212217
if (isDefault(config.jsonProvider(), defaultJsonProviderType)) {
213-
config = config.jsonProvider(new JacksonJsonProvider());
218+
config = config.jsonProvider(jsonProvider);
214219
}
215220
if (isDefault(config.mappingProvider(), defaultMappingProviderType)) {
216-
config = config.mappingProvider(new JacksonMappingProvider());
221+
config = config.mappingProvider(mappingProvider);
217222
}
218223
return config;
219224
}
220225

221-
private static <T> boolean isDefault(@Nullable T provider, Class<? extends T> defaultProviderType) {
226+
static <T> boolean isDefault(@Nullable T provider, Class<? extends T> defaultProviderType) {
222227
return (provider == null || defaultProviderType.isInstance(provider));
223228
}
224229

225230
}
226231

232+
private static final class JacksonConfigurer extends AbstractJacksonConfigurer {
233+
234+
static Configuration configure(Configuration config) {
235+
return configure(config, new JacksonJsonProvider(), new JacksonMappingProvider());
236+
}
237+
238+
}
239+
240+
private static final class Jackson2Configurer extends AbstractJacksonConfigurer {
241+
242+
static Configuration configure(Configuration config) {
243+
return configure(config, new com.jayway.jsonpath.spi.json.JacksonJsonProvider(),
244+
new com.jayway.jsonpath.spi.mapper.JacksonMappingProvider());
245+
}
246+
247+
}
248+
227249
}

spring-graphql/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ dependencies {
3333
compileOnly 'org.jetbrains.kotlinx:kotlinx-coroutines-core'
3434

3535
compileOnly 'tools.jackson.core:jackson-databind'
36+
compileOnly 'com.fasterxml.jackson.core:jackson-databind'
3637

3738
compileOnly('com.apollographql.federation:federation-graphql-java-support')
3839
compileOnly('com.netflix.graphql.dgs.codegen:graphql-dgs-codegen-shared-core') {
@@ -82,6 +83,8 @@ dependencies {
8283
testImplementation 'jakarta.validation:jakarta.validation-api'
8384
testImplementation 'com.jayway.jsonpath:json-path'
8485
testImplementation 'tools.jackson.core:jackson-databind'
86+
testImplementation 'com.fasterxml.jackson.core:jackson-databind'
87+
testImplementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
8588
testImplementation 'org.apache.tomcat.embed:tomcat-embed-el:10.0.21'
8689
testImplementation 'com.apollographql.federation:federation-graphql-java-support'
8790

@@ -93,6 +96,7 @@ dependencies {
9396
testFixturesApi 'org.junit.jupiter:junit-jupiter-engine'
9497
testFixturesApi 'com.squareup.okhttp3:mockwebserver'
9598
testFixturesApi 'tools.jackson.core:jackson-databind'
99+
testFixturesApi 'com.fasterxml.jackson.core:jackson-databind'
96100
}
97101

98102
test {

spring-graphql/src/main/java/org/springframework/graphql/client/AbstractGraphQlClientBuilder.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
import org.springframework.graphql.support.CachingDocumentSource;
3434
import org.springframework.graphql.support.DocumentSource;
3535
import org.springframework.graphql.support.ResourceDocumentSource;
36+
import org.springframework.http.codec.json.Jackson2JsonDecoder;
37+
import org.springframework.http.codec.json.Jackson2JsonEncoder;
3638
import org.springframework.http.codec.json.JacksonJsonDecoder;
3739
import org.springframework.http.codec.json.JacksonJsonEncoder;
3840
import org.springframework.util.Assert;
@@ -58,6 +60,8 @@ public abstract class AbstractGraphQlClientBuilder<B extends AbstractGraphQlClie
5860
protected static final boolean jacksonPresent = ClassUtils.isPresent(
5961
"tools.jackson.databind.ObjectMapper", AbstractGraphQlClientBuilder.class.getClassLoader());
6062

63+
protected static final boolean jackson2Present = ClassUtils.isPresent(
64+
"com.fasterxml.jackson.databind.ObjectMapper", AbstractGraphQlClientBuilder.class.getClassLoader());
6165

6266
private final List<GraphQlClientInterceptor> interceptors = new ArrayList<>();
6367

@@ -181,6 +185,10 @@ protected GraphQlClient buildGraphQlClient(GraphQlTransport transport) {
181185
this.jsonEncoder = (this.jsonEncoder == null) ? DefaultJacksonCodecs.encoder() : this.jsonEncoder;
182186
this.jsonDecoder = (this.jsonDecoder == null) ? DefaultJacksonCodecs.decoder() : this.jsonDecoder;
183187
}
188+
else if (jackson2Present) {
189+
this.jsonEncoder = (this.jsonEncoder == null) ? DefaultJackson2Codecs.encoder() : this.jsonEncoder;
190+
this.jsonDecoder = (this.jsonDecoder == null) ? DefaultJackson2Codecs.decoder() : this.jsonDecoder;
191+
}
184192

185193
return new DefaultGraphQlClient(this.documentSource,
186194
createExecuteChain(transport), createSubscriptionChain(transport), this.blockingTimeout);
@@ -243,4 +251,16 @@ static Decoder<?> decoder() {
243251

244252
}
245253

254+
@SuppressWarnings("removal")
255+
protected static class DefaultJackson2Codecs {
256+
257+
static Encoder<?> encoder() {
258+
return new Jackson2JsonEncoder();
259+
}
260+
261+
static Decoder<?> decoder() {
262+
return new Jackson2JsonDecoder();
263+
}
264+
}
265+
246266
}

spring-graphql/src/main/java/org/springframework/graphql/client/AbstractGraphQlClientSyncBuilder.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.springframework.graphql.support.ResourceDocumentSource;
3737
import org.springframework.http.converter.HttpMessageConverter;
3838
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
39+
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
3940
import org.springframework.util.Assert;
4041
import org.springframework.util.ClassUtils;
4142

@@ -60,6 +61,9 @@ public abstract class AbstractGraphQlClientSyncBuilder<B extends AbstractGraphQl
6061
protected static final boolean jacksonPresent = ClassUtils.isPresent(
6162
"tools.jackson.databind.ObjectMapper", AbstractGraphQlClientSyncBuilder.class.getClassLoader());
6263

64+
protected static final boolean jackson2Present = ClassUtils.isPresent(
65+
"com.fasterxml.jackson.databind.ObjectMapper", AbstractGraphQlClientSyncBuilder.class.getClassLoader());
66+
6367

6468
private final List<SyncGraphQlClientInterceptor> interceptors = new ArrayList<>();
6569

@@ -148,6 +152,10 @@ protected GraphQlClient buildGraphQlClient(SyncGraphQlTransport transport) {
148152
this.jsonConverter = (this.jsonConverter == null) ?
149153
DefaultJacksonConverter.initialize() : this.jsonConverter;
150154
}
155+
else if (jackson2Present) {
156+
this.jsonConverter = (this.jsonConverter == null) ?
157+
DefaultJackson2Converter.initialize() : this.jsonConverter;
158+
}
151159

152160
return new DefaultGraphQlClient(
153161
this.documentSource, createExecuteChain(transport), this.scheduler, this.blockingTimeout);
@@ -193,4 +201,12 @@ static HttpMessageConverter<Object> initialize() {
193201
}
194202
}
195203

204+
@SuppressWarnings("removal")
205+
private static final class DefaultJackson2Converter {
206+
207+
static HttpMessageConverter<Object> initialize() {
208+
return new MappingJackson2HttpMessageConverter();
209+
}
210+
}
211+
196212
}

spring-graphql/src/main/java/org/springframework/graphql/client/DefaultRSocketGraphQlClientBuilder.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ private static RSocketRequester.Builder initRSocketRequestBuilder() {
7676
.decoder(DefaultJacksonCodecs.decoder())
7777
.build());
7878
}
79+
else if (jackson2Present) {
80+
requesterBuilder.rsocketStrategies(
81+
RSocketStrategies.builder()
82+
.encoder(DefaultJackson2Codecs.encoder())
83+
.decoder(DefaultJackson2Codecs.decoder())
84+
.build());
85+
}
7986
return requesterBuilder;
8087
}
8188

spring-graphql/src/main/java/org/springframework/graphql/data/query/JsonKeysetCursorStrategy.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,11 @@
4141
import org.springframework.http.codec.DecoderHttpMessageReader;
4242
import org.springframework.http.codec.EncoderHttpMessageWriter;
4343
import org.springframework.http.codec.ServerCodecConfigurer;
44+
import org.springframework.http.codec.json.Jackson2JsonDecoder;
45+
import org.springframework.http.codec.json.Jackson2JsonEncoder;
4446
import org.springframework.http.codec.json.JacksonJsonDecoder;
4547
import org.springframework.http.codec.json.JacksonJsonEncoder;
48+
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
4649
import org.springframework.util.Assert;
4750
import org.springframework.util.ClassUtils;
4851
import org.springframework.util.MimeTypeUtils;
@@ -62,6 +65,9 @@ public final class JsonKeysetCursorStrategy implements CursorStrategy<Map<String
6265
private static final boolean jacksonPresent = ClassUtils.isPresent(
6366
"tools.jackson.databind.ObjectMapper", JsonKeysetCursorStrategy.class.getClassLoader());
6467

68+
private static final boolean jackson2Present = ClassUtils.isPresent(
69+
"com.fasterxml.jackson.databind.ObjectMapper", JsonKeysetCursorStrategy.class.getClassLoader());
70+
6571

6672
private final Encoder<?> encoder;
6773

@@ -82,6 +88,9 @@ private static ServerCodecConfigurer initCodecConfigurer() {
8288
if (jacksonPresent) {
8389
JacksonObjectMapperCustomizer.customize(configurer);
8490
}
91+
else if (jackson2Present) {
92+
Jackson2ObjectMapperCustomizer.customize(configurer);
93+
}
8594
return configurer;
8695
}
8796

@@ -161,4 +170,31 @@ static void customize(CodecConfigurer configurer) {
161170

162171
}
163172

173+
/**
174+
* Customizes the {@link ObjectMapper} to use default typing that supports
175+
* {@link Date}, {@link Calendar}, and classes in {@code java.time}.
176+
*/
177+
@SuppressWarnings("removal")
178+
private static final class Jackson2ObjectMapperCustomizer {
179+
180+
static void customize(CodecConfigurer configurer) {
181+
182+
com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator validator =
183+
com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator.builder()
184+
.allowIfBaseType(Map.class)
185+
.allowIfSubType("java.time.")
186+
.allowIfSubType(Calendar.class)
187+
.allowIfSubType(Date.class)
188+
.build();
189+
190+
com.fasterxml.jackson.databind.ObjectMapper mapper = Jackson2ObjectMapperBuilder.json().build();
191+
mapper.activateDefaultTyping(validator, com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping.NON_FINAL);
192+
mapper.enable(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
193+
194+
configurer.defaultCodecs().jacksonJsonDecoder(new Jackson2JsonDecoder(mapper));
195+
configurer.defaultCodecs().jacksonJsonEncoder(new Jackson2JsonEncoder(mapper));
196+
}
197+
198+
}
199+
164200
}

spring-graphql/src/test/java/org/springframework/graphql/ResponseHelper.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.springframework.core.ParameterizedTypeReference;
3838
import org.springframework.core.ResolvableType;
3939
import org.springframework.lang.Nullable;
40+
import org.springframework.util.ClassUtils;
4041
import org.springframework.util.StringUtils;
4142

4243
import static org.assertj.core.api.Assertions.assertThat;
@@ -52,6 +53,9 @@ public class ResponseHelper {
5253

5354
private static final Log logger = LogFactory.getLog(ResponseHelper.class);
5455

56+
protected static final boolean jacksonPresent = ClassUtils.isPresent(
57+
"tools.jackson.databind.ObjectMapper", ResponseHelper.class.getClassLoader());
58+
5559

5660
private final DocumentContext documentContext;
5761

@@ -66,10 +70,18 @@ private ResponseHelper(Map<String, Object> responseMap, List<GraphQLError> error
6670
}
6771

6872
private static Configuration initJsonPathConfig() {
69-
return Configuration.builder()
70-
.jsonProvider(new JacksonJsonProvider())
71-
.mappingProvider(new JacksonMappingProvider())
72-
.build();
73+
if (jacksonPresent) {
74+
return Configuration.builder()
75+
.jsonProvider(new JacksonJsonProvider())
76+
.mappingProvider(new JacksonMappingProvider())
77+
.build();
78+
}
79+
else {
80+
return Configuration.builder()
81+
.jsonProvider(new com.jayway.jsonpath.spi.json.JacksonJsonProvider())
82+
.mappingProvider(new com.jayway.jsonpath.spi.mapper.JacksonMappingProvider())
83+
.build();
84+
}
7385
}
7486

7587

0 commit comments

Comments
 (0)