Skip to content

Commit d1815a1

Browse files
JordanJLopezJordan LopezCopilotCopilot
authored
BREAKING CHANGE: Upgrade to Jackson 3 (#2162)
Helpful Context: - Jackson 3 Upgrade Guide https://github.com/FasterXML/jackson/blob/main/jackson3/MIGRATING_TO_JACKSON_3.md - Spring Boot 4 Upgrade Guide - Jackson Section https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Migration-Guide#upgrading-jackson In my previous Spring Boot breaking change PR, I assumed that since graphql-java used Jackson 2 that we'd be pinned to that as well. However, on further review, they're only using it as a [test dependency](https://github.com/graphql-java/graphql-java/blob/e0ca0613e1c15a7a6c4d012e1ebba37e091ac682/build.gradle#L149). So, this PR takes on the migration to Jackson 3. Big highlights: * Use the Spring-preferred `spring-boot-jackson` instead of `spring-boot-jackson2` * Bring in ktor support for jackson 3 by moving from `ktor-serialization-jackson` to `ktor-serialization-jackson3` * Added in [3.4.0](https://github.com/ktorio/ktor/releases/tag/3.4.0) with `https://github.com/ktorio/ktor/pull/5301` * The package for most Jackson classes changed from `com.fasterxml.jackson.*` to `tools.jackson.*` * With the exception of `*.annotation.*` classes, those remain under the old package path * `ObjectMapper`s are now immutable, the new pattern is provide builder modifications --------- Co-authored-by: Jordan Lopez <jordlopez@expediagroup.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: JordanJLopez <8593393+JordanJLopez@users.noreply.github.com>
1 parent ef99f1e commit d1815a1

101 files changed

Lines changed: 517 additions & 335 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

clients/graphql-kotlin-client-jackson/src/main/kotlin/com/expediagroup/graphql/client/jackson/GraphQLClientJacksonSerializer.kt

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,21 @@ import com.expediagroup.graphql.client.jackson.types.UndefinedFilter
2222
import com.expediagroup.graphql.client.serializer.GraphQLClientSerializer
2323
import com.expediagroup.graphql.client.types.GraphQLClientRequest
2424
import com.fasterxml.jackson.annotation.JsonInclude
25-
import com.fasterxml.jackson.databind.DeserializationFeature
26-
import com.fasterxml.jackson.databind.JavaType
27-
import com.fasterxml.jackson.databind.ObjectMapper
28-
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
25+
import tools.jackson.databind.JavaType
26+
import tools.jackson.databind.ObjectMapper
27+
import tools.jackson.databind.cfg.EnumFeature
28+
import tools.jackson.databind.json.JsonMapper
29+
import tools.jackson.module.kotlin.jacksonMapperBuilder
2930
import java.util.concurrent.ConcurrentHashMap
3031
import kotlin.reflect.KClass
3132

3233
/**
3334
* Jackson based GraphQL request/response serializer.
3435
*/
35-
class GraphQLClientJacksonSerializer(private val mapper: ObjectMapper = jacksonObjectMapper()) : GraphQLClientSerializer {
36+
class GraphQLClientJacksonSerializer(mapper: JsonMapper = jacksonMapperBuilder().build()) : GraphQLClientSerializer {
37+
private val mapper: ObjectMapper = configureMapper(mapper)
3638
private val typeCache = ConcurrentHashMap<KClass<*>, JavaType>()
3739

38-
init {
39-
mapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)
40-
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL)
41-
mapper.configOverride(OptionalInput::class.java).include = JsonInclude.Value.empty().withValueInclusion(JsonInclude.Include.CUSTOM).withValueFilter(UndefinedFilter::class.java)
42-
}
43-
4440
override fun serialize(request: GraphQLClientRequest<*>): String = mapper.writeValueAsString(request)
4541

4642
override fun serialize(requests: List<GraphQLClientRequest<*>>): String = mapper.writeValueAsString(requests)
@@ -66,4 +62,18 @@ class GraphQLClientJacksonSerializer(private val mapper: ObjectMapper = jacksonO
6662
typeCache.computeIfAbsent(resultType) {
6763
mapper.typeFactory.constructParametricType(JacksonGraphQLResponse::class.java, resultType.java)
6864
}
65+
66+
companion object {
67+
private fun configureMapper(mapper: JsonMapper): JsonMapper = mapper.rebuild()
68+
.enable(EnumFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)
69+
.changeDefaultPropertyInclusion { it.withValueInclusion(JsonInclude.Include.NON_NULL) }
70+
.withConfigOverride(OptionalInput::class.java) { cfg ->
71+
cfg.setInclude(
72+
JsonInclude.Value.empty()
73+
.withValueInclusion(JsonInclude.Include.CUSTOM)
74+
.withValueFilter(UndefinedFilter::class.java)
75+
)
76+
}
77+
.build()
78+
}
6979
}

clients/graphql-kotlin-client-jackson/src/main/kotlin/com/expediagroup/graphql/client/jackson/serializers/OptionalInputSerializer.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,24 @@
1717
package com.expediagroup.graphql.client.jackson.serializers
1818

1919
import com.expediagroup.graphql.client.jackson.types.OptionalInput
20-
import com.fasterxml.jackson.core.JsonGenerator
21-
import com.fasterxml.jackson.databind.JsonSerializer
22-
import com.fasterxml.jackson.databind.SerializerProvider
20+
import tools.jackson.core.JsonGenerator
21+
import tools.jackson.databind.SerializationContext
22+
import tools.jackson.databind.ValueSerializer
2323

24-
class OptionalInputSerializer : JsonSerializer<OptionalInput<*>>() {
24+
class OptionalInputSerializer : ValueSerializer<OptionalInput<*>>() {
2525

26-
override fun isEmpty(provider: SerializerProvider, value: OptionalInput<*>?): Boolean {
26+
override fun isEmpty(ctxt: SerializationContext, value: OptionalInput<*>?): Boolean {
2727
return value == OptionalInput.Undefined
2828
}
2929

30-
override fun serialize(value: OptionalInput<*>, gen: JsonGenerator, serializers: SerializerProvider) {
30+
override fun serialize(value: OptionalInput<*>, gen: JsonGenerator, ctxt: SerializationContext) {
3131
when (value) {
3232
is OptionalInput.Undefined -> return
3333
is OptionalInput.Defined -> {
3434
if (value.value == null) {
35-
serializers.defaultNullValueSerializer.serialize(value.value, gen, serializers)
35+
ctxt.defaultNullValueSerializer.serialize(value.value, gen, ctxt)
3636
} else {
37-
serializers.findValueSerializer(value.value::class.java).serialize(value.value, gen, serializers)
37+
ctxt.findValueSerializer(value.value::class.java).serialize(value.value, gen, ctxt)
3838
}
3939
}
4040
}

clients/graphql-kotlin-client-jackson/src/main/kotlin/com/expediagroup/graphql/client/jackson/types/OptionalInput.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package com.expediagroup.graphql.client.jackson.types
1919
import com.expediagroup.graphql.client.jackson.serializers.OptionalInputSerializer
2020
import com.fasterxml.jackson.annotation.JsonCreator
2121
import com.fasterxml.jackson.annotation.JsonValue
22-
import com.fasterxml.jackson.databind.annotation.JsonSerialize
22+
import tools.jackson.databind.annotation.JsonSerialize
2323

2424
@JsonSerialize(using = OptionalInputSerializer::class)
2525
sealed class OptionalInput<out T> {

clients/graphql-kotlin-client-jackson/src/test/kotlin/com/expediagroup/graphql/client/jackson/GraphQLClientJacksonSerializerTest.kt

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,22 @@ import com.expediagroup.graphql.client.jackson.types.JacksonGraphQLError
3232
import com.expediagroup.graphql.client.jackson.types.JacksonGraphQLResponse
3333
import com.expediagroup.graphql.client.jackson.types.JacksonGraphQLSourceLocation
3434
import com.expediagroup.graphql.client.jackson.types.OptionalInput
35-
import com.fasterxml.jackson.databind.SerializationFeature
36-
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
3735
import org.junit.jupiter.api.Test
36+
import tools.jackson.databind.SerializationFeature
37+
import tools.jackson.module.kotlin.jacksonMapperBuilder
3838
import java.util.UUID
3939
import kotlin.test.assertEquals
4040

4141
class GraphQLClientJacksonSerializerTest {
4242

43-
private val testMapper = jacksonObjectMapper()
44-
.enable(SerializationFeature.INDENT_OUTPUT)
43+
private val testMapper = jacksonMapperBuilder().enable(SerializationFeature.INDENT_OUTPUT).build()
4544
private val serializer = GraphQLClientJacksonSerializer(testMapper)
4645

46+
private fun assertSerializedJsonEquals(expected: String, actual: String) {
47+
// Check the contents rather than the string order
48+
assertEquals(testMapper.readTree(expected), testMapper.readTree(actual))
49+
}
50+
4751
@Test
4852
fun `verify we can serialize GraphQLClientRequest`() {
4953
val testQuery = FirstQuery(FirstQuery.Variables(input = 1.0f))
@@ -58,7 +62,7 @@ class GraphQLClientJacksonSerializerTest {
5862
""".trimMargin()
5963

6064
val serialized = serializer.serialize(testQuery)
61-
assertEquals(expected, serialized)
65+
assertSerializedJsonEquals(expected, serialized)
6266
}
6367

6468
@Test
@@ -78,7 +82,7 @@ class GraphQLClientJacksonSerializerTest {
7882
""".trimMargin()
7983

8084
val serialized = serializer.serialize(queries)
81-
assertEquals(expected, serialized)
85+
assertSerializedJsonEquals(expected, serialized)
8286
}
8387

8488
@Test
@@ -193,7 +197,7 @@ class GraphQLClientJacksonSerializerTest {
193197
""".trimMargin()
194198

195199
val serialized = serializer.serialize(scalarQuery)
196-
assertEquals(expected, serialized)
200+
assertSerializedJsonEquals(expected, serialized)
197201
}
198202

199203
@Test
@@ -239,7 +243,7 @@ class GraphQLClientJacksonSerializerTest {
239243
""".trimMargin()
240244

241245
val serialized = serializer.serialize(query)
242-
assertEquals(expected, serialized)
246+
assertSerializedJsonEquals(expected, serialized)
243247
}
244248

245249
@Test
@@ -276,7 +280,7 @@ class GraphQLClientJacksonSerializerTest {
276280
""".trimMargin()
277281

278282
val serialized = serializer.serialize(query)
279-
assertEquals(expected, serialized)
283+
assertSerializedJsonEquals(expected, serialized)
280284
}
281285

282286
@Test
@@ -309,7 +313,7 @@ class GraphQLClientJacksonSerializerTest {
309313
|}
310314
""".trimMargin()
311315
val serialized = serializer.serialize(query)
312-
assertEquals(expected, serialized)
316+
assertSerializedJsonEquals(expected, serialized)
313317
}
314318

315319
@Test
@@ -325,7 +329,7 @@ class GraphQLClientJacksonSerializerTest {
325329
|}
326330
""".trimMargin()
327331
val serialized = serializer.serialize(query)
328-
assertEquals(expected, serialized)
332+
assertSerializedJsonEquals(expected, serialized)
329333
}
330334

331335
@Test
@@ -345,6 +349,6 @@ class GraphQLClientJacksonSerializerTest {
345349
""".trimMargin()
346350

347351
val serialized = serializer.serialize(entitiesQuery)
348-
assertEquals(expected, serialized)
352+
assertSerializedJsonEquals(expected, serialized)
349353
}
350354
}

clients/graphql-kotlin-client-jackson/src/test/kotlin/com/expediagroup/graphql/client/jackson/data/EntitiesQuery.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import com.expediagroup.graphql.client.jackson.data.entitiesquery._Entity
2020
import com.expediagroup.graphql.client.jackson.data.scalars.AnyToAnyConverter
2121
import com.expediagroup.graphql.client.types.GraphQLClientRequest
2222
import com.fasterxml.jackson.annotation.JsonProperty
23-
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
24-
import com.fasterxml.jackson.databind.annotation.JsonSerialize
23+
import tools.jackson.databind.annotation.JsonDeserialize
24+
import tools.jackson.databind.annotation.JsonSerialize
2525
import kotlin.reflect.KClass
2626

2727
class EntitiesQuery(
@@ -34,8 +34,8 @@ class EntitiesQuery(
3434
override fun responseType(): KClass<Result> = Result::class
3535

3636
data class Variables(
37-
@JsonSerialize(contentConverter = AnyToAnyConverter::class)
38-
@JsonDeserialize(contentConverter = AnyToAnyConverter::class)
37+
@get:JsonSerialize(contentConverter = AnyToAnyConverter::class)
38+
@get:JsonDeserialize(contentConverter = AnyToAnyConverter::class)
3939
@get:JsonProperty("representations")
4040
public val representations: List<Any>,
4141
)

clients/graphql-kotlin-client-jackson/src/test/kotlin/com/expediagroup/graphql/client/jackson/data/ScalarQuery.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import com.expediagroup.graphql.client.jackson.data.scalars.AnyToUUIDConverter
2020
import com.expediagroup.graphql.client.jackson.data.scalars.UUIDToAnyConverter
2121
import com.expediagroup.graphql.client.types.GraphQLClientRequest
2222
import com.fasterxml.jackson.annotation.JsonProperty
23-
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
24-
import com.fasterxml.jackson.databind.annotation.JsonSerialize
23+
import tools.jackson.databind.annotation.JsonDeserialize
24+
import tools.jackson.databind.annotation.JsonSerialize
2525
import java.util.UUID
2626
import kotlin.reflect.KClass
2727

@@ -40,16 +40,16 @@ class ScalarQuery(
4040
data class Variables(
4141
@get:JsonProperty("alias")
4242
val alias: ID? = null,
43-
@JsonSerialize(converter = UUIDToAnyConverter::class)
44-
@JsonDeserialize(converter = AnyToUUIDConverter::class)
43+
@get:JsonSerialize(converter = UUIDToAnyConverter::class)
44+
@get:JsonDeserialize(converter = AnyToUUIDConverter::class)
4545
@get:JsonProperty("custom")
4646
val custom: UUID? = null
4747
)
4848

4949
data class Result(
5050
val scalarAlias: ID,
51-
@JsonSerialize(converter = UUIDToAnyConverter::class)
52-
@JsonDeserialize(converter = AnyToUUIDConverter::class)
51+
@get:JsonSerialize(converter = UUIDToAnyConverter::class)
52+
@get:JsonDeserialize(converter = AnyToUUIDConverter::class)
5353
val customScalar: UUID
5454
)
5555
}

clients/graphql-kotlin-client-jackson/src/test/kotlin/com/expediagroup/graphql/client/jackson/data/scalars/AnyToAnyConverter.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
package com.expediagroup.graphql.client.jackson.data.scalars
1818

1919
import com.expediagroup.graphql.client.converter.ScalarConverter
20-
import com.fasterxml.jackson.databind.util.StdConverter
20+
import com.fasterxml.jackson.annotation.JsonProperty
21+
import tools.jackson.databind.util.StdConverter
2122
import kotlin.Any
2223

2324
class AnyToAnyConverter : StdConverter<Any, Any>() {
@@ -34,5 +35,6 @@ class AnyScalarConverter : ScalarConverter<Any> {
3435

3536
// representation would not be part of the generated sources
3637
data class ProductEntityRepresentation(val id: String) {
38+
@get:JsonProperty("__typename")
3739
val __typename: String = "Product"
3840
}

clients/graphql-kotlin-client-jackson/src/test/kotlin/com/expediagroup/graphql/client/jackson/data/scalars/UUIDConverters.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
package com.expediagroup.graphql.client.jackson.data.scalars
1818

1919
import com.expediagroup.graphql.client.converter.ScalarConverter
20-
import com.fasterxml.jackson.databind.util.StdConverter
20+
import tools.jackson.databind.util.StdConverter
2121
import java.util.UUID
2222
import kotlin.Any
2323

clients/graphql-kotlin-client-jackson/src/test/kotlin/com/expediagroup/graphql/client/jackson/serializers/OptionalInputSerializerTest.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,15 @@ package com.expediagroup.graphql.client.jackson.serializers
1818

1919
import com.expediagroup.graphql.client.jackson.types.OptionalInput
2020
import com.fasterxml.jackson.annotation.JsonInclude
21-
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
2221
import org.junit.jupiter.api.Test
22+
import tools.jackson.module.kotlin.jacksonMapperBuilder
2323
import kotlin.test.assertEquals
2424

2525
class OptionalInputSerializerTest {
2626

27-
private val mapper = jacksonObjectMapper()
28-
init {
29-
mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
30-
}
27+
private val mapper = jacksonMapperBuilder()
28+
.changeDefaultPropertyInclusion { incl -> incl.withValueInclusion(JsonInclude.Include.NON_EMPTY) }
29+
.build()
3130

3231
@Test
3332
fun `verify undefined value is serialized to empty JSON`() {

0 commit comments

Comments
 (0)