Skip to content

Commit cdc1456

Browse files
Add Jackson 3 support (compileOnly); replace json-patch with zjsonpatch (#28)
- Add serialization/jackson3 mirroring jackson2 (DataSourceModule + builder-based addDataSourceModule, serializers/deserializers), ported to the tools.jackson API. - Add Jackson2JsonHandler and Jackson3JsonHandler implementing the SDK JsonHandler, both using zjsonpatch for RFC 6902 diff/patch in place of the Jackson-2-only json-patch (fge) library, which is excluded from the dependency graph. - SimpleDataSourceFactory: createJackson2JsonHandler/defaultJackson2JsonHandler now return Jackson2JsonHandler; add lazy defaultJackson3ObjectMapper, createJackson3JsonHandler and defaultJackson3JsonHandler (lazy so the compileOnly Jackson 3 classes never load on the Jackson 2 runtime path). - spring starter wires the new Jackson2JsonHandler and drops json-patch. - Catalog: add Jackson 3 (3.1.3, compileOnly) and zjsonpatch (0.6.2); remove json-patch. Add jackson3 serialization + handler round-trip tests and a Jackson 2-vs-3 serialization JMH benchmark.
1 parent 1f41236 commit cdc1456

27 files changed

Lines changed: 1020 additions & 21 deletions

gradle/libs.versions.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
springBoot = "3.5.14"
33
kotlin = "2.2.0"
44
kotlinCollectionsImmutable = "0.4.0"
5-
jsonPatch = "1.13"
5+
jackson3 = "3.1.3"
6+
zjsonpatch = "0.6.2"
67
jmh = "1.37"
78
jmh-plugin = "0.7.3"
89
kotlinpoet = "2.3.0"
@@ -30,7 +31,9 @@ spring-dependency-management-plugin = "1.1.7"
3031

3132
[libraries]
3233
kotlin-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinCollectionsImmutable" }
33-
json-patch = { module = "com.github.java-json-tools:json-patch", version.ref = "jsonPatch" }
34+
jackson3-databind = { module = "tools.jackson.core:jackson-databind", version.ref = "jackson3" }
35+
jackson3-module-kotlin = { module = "tools.jackson.module:jackson-module-kotlin", version.ref = "jackson3" }
36+
zjsonpatch = { module = "io.github.vishwakarma:zjsonpatch", version.ref = "zjsonpatch" }
3437
kotlinpoet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinpoet" }
3538
fory-core = { module = "org.apache.fory:fory-core", version.ref = "fory" }
3639
fory-kotlin = { module = "org.apache.fory:fory-kotlin", version.ref = "fory" }

spring/build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ dependencies {
1515
implementation("org.springframework.boot:spring-boot-starter")
1616
implementation("org.springframework.boot:spring-boot-starter-json")
1717
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
18-
implementation(libs.json.patch)
1918
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
2019
implementation("org.slf4j:slf4j-api")
2120

spring/src/main/kotlin/com/caplin/integration/datasourcex/spring/internal/DataSourceAutoConfiguration.kt

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ package com.caplin.integration.datasourcex.spring.internal
33
import com.caplin.datasource.DataSource
44
import com.caplin.datasource.internal.configuration.AttributeConfiguration.DATASRC_LOCAL_LABEL
55
import com.caplin.datasource.internal.configuration.AttributeConfiguration.DATASRC_NAME
6-
import com.caplin.datasource.messaging.json.JacksonJsonHandler
76
import com.caplin.integration.datasourcex.spring.DataSourceConfigurationProperties
87
import com.caplin.integration.datasourcex.util.SimpleDataSourceConfig.Discovery
98
import com.caplin.integration.datasourcex.util.SimpleDataSourceConfig.Peer
109
import com.caplin.integration.datasourcex.util.SimpleDataSourceFactory.createDataSource
10+
import com.caplin.integration.datasourcex.util.SimpleDataSourceFactory.createJackson2JsonHandler
1111
import com.caplin.integration.datasourcex.util.getLogger
1212
import com.fasterxml.jackson.databind.ObjectMapper
1313
import java.nio.file.Paths
@@ -91,13 +91,7 @@ internal class DataSourceAutoConfiguration {
9191
}
9292
)
9393
}
94-
.apply {
95-
extraConfiguration.jsonHandler =
96-
JacksonJsonHandler(
97-
Logger.getLogger(JacksonJsonHandler::class.qualifiedName),
98-
objectMapper,
99-
)
100-
}
94+
.apply { extraConfiguration.jsonHandler = createJackson2JsonHandler(objectMapper) }
10195
}
10296

10397
@Bean

util/api/datasourcex-util.api

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,12 @@ public final class com/caplin/integration/datasourcex/util/SimpleDataSourceFacto
129129
public static final field INSTANCE Lcom/caplin/integration/datasourcex/util/SimpleDataSourceFactory;
130130
public static final fun createDataSource (Lcom/caplin/integration/datasourcex/util/SimpleDataSourceConfig;Lcom/caplin/datasource/messaging/json/JsonHandler;)Lcom/caplin/datasource/DataSource;
131131
public static synthetic fun createDataSource$default (Lcom/caplin/integration/datasourcex/util/SimpleDataSourceConfig;Lcom/caplin/datasource/messaging/json/JsonHandler;ILjava/lang/Object;)Lcom/caplin/datasource/DataSource;
132-
public static final fun createJackson2JsonHandler (Lcom/fasterxml/jackson/databind/ObjectMapper;)Lcom/caplin/datasource/messaging/json/JacksonJsonHandler;
133-
public final fun getDefaultJackson2JsonHandler ()Lcom/caplin/datasource/messaging/json/JacksonJsonHandler;
132+
public static final fun createJackson2JsonHandler (Lcom/fasterxml/jackson/databind/ObjectMapper;)Lcom/caplin/integration/datasourcex/util/serialization/jackson2/Jackson2JsonHandler;
133+
public static final fun createJackson3JsonHandler (Ltools/jackson/databind/ObjectMapper;)Lcom/caplin/integration/datasourcex/util/serialization/jackson3/Jackson3JsonHandler;
134+
public final fun getDefaultJackson2JsonHandler ()Lcom/caplin/integration/datasourcex/util/serialization/jackson2/Jackson2JsonHandler;
134135
public final fun getDefaultJackson2ObjectMapper ()Lcom/fasterxml/jackson/databind/ObjectMapper;
136+
public final fun getDefaultJackson3JsonHandler ()Lcom/caplin/integration/datasourcex/util/serialization/jackson3/Jackson3JsonHandler;
137+
public final fun getDefaultJackson3ObjectMapper ()Ltools/jackson/databind/ObjectMapper;
135138
}
136139

137140
public final class com/caplin/integration/datasourcex/util/TimeoutKt {
@@ -475,3 +478,43 @@ public final class com/caplin/integration/datasourcex/util/serialization/jackson
475478
public static final fun registerDataSourceModule (Lcom/fasterxml/jackson/databind/ObjectMapper;)Lcom/fasterxml/jackson/databind/ObjectMapper;
476479
}
477480

481+
public final class com/caplin/integration/datasourcex/util/serialization/jackson2/Jackson2JsonHandler : com/caplin/datasource/messaging/json/JsonHandler {
482+
public fun <init> (Lcom/fasterxml/jackson/databind/ObjectMapper;)V
483+
public fun diff (Lcom/fasterxml/jackson/databind/JsonNode;Lcom/fasterxml/jackson/databind/JsonNode;)Lcom/fasterxml/jackson/databind/JsonNode;
484+
public synthetic fun diff (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
485+
public fun format (Lcom/fasterxml/jackson/databind/JsonNode;)Ljava/lang/String;
486+
public synthetic fun format (Ljava/lang/Object;)Ljava/lang/String;
487+
public fun parse (Ljava/lang/String;)Lcom/fasterxml/jackson/databind/JsonNode;
488+
public synthetic fun parse (Ljava/lang/String;)Ljava/lang/Object;
489+
public fun patch (Lcom/fasterxml/jackson/databind/JsonNode;Lcom/fasterxml/jackson/databind/JsonNode;)Lcom/fasterxml/jackson/databind/JsonNode;
490+
public synthetic fun patch (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
491+
public fun toJsonTree (Ljava/lang/Object;)Lcom/fasterxml/jackson/databind/JsonNode;
492+
public synthetic fun toJsonTree (Ljava/lang/Object;)Ljava/lang/Object;
493+
public fun toObject (Lcom/fasterxml/jackson/databind/JsonNode;Ljava/lang/Class;)Ljava/lang/Object;
494+
public synthetic fun toObject (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object;
495+
}
496+
497+
public final class com/caplin/integration/datasourcex/util/serialization/jackson3/DataSourceModule : tools/jackson/databind/module/SimpleModule {
498+
public static final field INSTANCE Lcom/caplin/integration/datasourcex/util/serialization/jackson3/DataSourceModule;
499+
}
500+
501+
public final class com/caplin/integration/datasourcex/util/serialization/jackson3/DataSourceModuleKt {
502+
public static final fun addDataSourceModule (Ltools/jackson/databind/json/JsonMapper$Builder;)Ltools/jackson/databind/json/JsonMapper$Builder;
503+
}
504+
505+
public final class com/caplin/integration/datasourcex/util/serialization/jackson3/Jackson3JsonHandler : com/caplin/datasource/messaging/json/JsonHandler {
506+
public fun <init> (Ltools/jackson/databind/ObjectMapper;)V
507+
public synthetic fun diff (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
508+
public fun diff (Ltools/jackson/databind/JsonNode;Ltools/jackson/databind/JsonNode;)Ltools/jackson/databind/JsonNode;
509+
public synthetic fun format (Ljava/lang/Object;)Ljava/lang/String;
510+
public fun format (Ltools/jackson/databind/JsonNode;)Ljava/lang/String;
511+
public synthetic fun parse (Ljava/lang/String;)Ljava/lang/Object;
512+
public fun parse (Ljava/lang/String;)Ltools/jackson/databind/JsonNode;
513+
public synthetic fun patch (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
514+
public fun patch (Ltools/jackson/databind/JsonNode;Ltools/jackson/databind/JsonNode;)Ltools/jackson/databind/JsonNode;
515+
public synthetic fun toJsonTree (Ljava/lang/Object;)Ljava/lang/Object;
516+
public fun toJsonTree (Ljava/lang/Object;)Ltools/jackson/databind/JsonNode;
517+
public synthetic fun toObject (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object;
518+
public fun toObject (Ltools/jackson/databind/JsonNode;Ljava/lang/Class;)Ljava/lang/Object;
519+
}
520+

util/build.gradle.kts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,23 @@ description = "Utility classes for DataSource extensions"
77

88
dependencies {
99
api(platform(libs.spring.boot.dependencies))
10-
api(libs.datasource)
10+
// We supply our own JsonHandler implementations (see serialization.jackson2/jackson3) instead of
11+
// the SDK's JacksonJsonHandler, so the Jackson-2-only json-patch library is no longer needed at
12+
// runtime. diff/patch is handled by zjsonpatch (which supports both Jackson 2 and 3).
13+
api(libs.datasource) { exclude(group = "com.github.java-json-tools", module = "json-patch") }
1114
api("org.slf4j:slf4j-api")
1215
api("org.jetbrains.kotlinx:kotlinx-coroutines-core")
1316
api("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm")
1417
api("com.fasterxml.jackson.core:jackson-core")
1518
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
1619
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
20+
implementation(libs.zjsonpatch)
21+
22+
// Jackson 3 support is opt-in: consumers that want the Jackson 3 serializers / JsonHandler must
23+
// bring Jackson 3 onto their own classpath. Kept compileOnly here so it stays off the default
24+
// (Jackson 2) runtime path.
25+
compileOnly(libs.jackson3.databind)
26+
compileOnly(libs.jackson3.module.kotlin)
1727

1828
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
1929
implementation("org.jetbrains.kotlin:kotlin-reflect")
@@ -30,9 +40,13 @@ dependencies {
3040
testImplementation(libs.kotest.runner)
3141
testImplementation(libs.fory.core)
3242
testImplementation(libs.fory.kotlin)
43+
testImplementation(libs.jackson3.databind)
44+
testImplementation(libs.jackson3.module.kotlin)
3345

3446
jmh(libs.jmh.core)
3547
jmh(libs.jmh.generator)
48+
jmh(libs.jackson3.databind)
49+
jmh(libs.jackson3.module.kotlin)
3650
}
3751

3852
jmh { duplicateClassesStrategy.set(DuplicatesStrategy.EXCLUDE) }
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.caplin.integration.datasourcex.util.serialization
2+
3+
import com.caplin.integration.datasourcex.util.flow.FlowMapStreamEvent
4+
import com.caplin.integration.datasourcex.util.flow.mutableFlowMapOf
5+
import com.caplin.integration.datasourcex.util.serialization.jackson2.registerDataSourceModule
6+
import com.caplin.integration.datasourcex.util.serialization.jackson3.addDataSourceModule
7+
import com.fasterxml.jackson.core.type.TypeReference as Jackson2TypeReference
8+
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
9+
import java.util.concurrent.TimeUnit
10+
import org.openjdk.jmh.annotations.Benchmark
11+
import org.openjdk.jmh.annotations.BenchmarkMode
12+
import org.openjdk.jmh.annotations.Fork
13+
import org.openjdk.jmh.annotations.Measurement
14+
import org.openjdk.jmh.annotations.Mode
15+
import org.openjdk.jmh.annotations.OutputTimeUnit
16+
import org.openjdk.jmh.annotations.Scope
17+
import org.openjdk.jmh.annotations.Setup
18+
import org.openjdk.jmh.annotations.State
19+
import org.openjdk.jmh.annotations.Warmup
20+
import tools.jackson.core.type.TypeReference as Jackson3TypeReference
21+
import tools.jackson.module.kotlin.jacksonMapperBuilder
22+
23+
/**
24+
* Compares Jackson 2 and Jackson 3 throughput for serializing and deserializing a representative
25+
* DataSource event (a 50-entry [FlowMapStreamEvent.InitialState]) through the DataSource module.
26+
*/
27+
@State(Scope.Benchmark)
28+
@BenchmarkMode(Mode.Throughput)
29+
@OutputTimeUnit(TimeUnit.MILLISECONDS)
30+
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
31+
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
32+
@Fork(1)
33+
open class JacksonSerializationBenchmark {
34+
35+
private val jackson2 = jacksonObjectMapper().registerDataSourceModule()
36+
private val jackson3 = jacksonMapperBuilder().addDataSourceModule().build()
37+
38+
private val jackson2Type = object : Jackson2TypeReference<FlowMapStreamEvent<String, Int>>() {}
39+
private val jackson3Type = object : Jackson3TypeReference<FlowMapStreamEvent<String, Int>>() {}
40+
41+
private lateinit var event: FlowMapStreamEvent<String, Int>
42+
private lateinit var json: String
43+
44+
@Setup
45+
fun setup() {
46+
val map = mutableFlowMapOf<String, Int>()
47+
repeat(50) { map.put("key$it", it) }
48+
event = FlowMapStreamEvent.InitialState(map.asMap())
49+
json = jackson2.writeValueAsString(event)
50+
}
51+
52+
@Benchmark fun serializeJackson2(): String = jackson2.writeValueAsString(event)
53+
54+
@Benchmark fun serializeJackson3(): String = jackson3.writeValueAsString(event)
55+
56+
@Benchmark
57+
fun deserializeJackson2(): FlowMapStreamEvent<String, Int> =
58+
jackson2.readValue(json, jackson2Type)
59+
60+
@Benchmark
61+
fun deserializeJackson3(): FlowMapStreamEvent<String, Int> =
62+
jackson3.readValue(json, jackson3Type)
63+
}

util/src/main/kotlin/com/caplin/integration/datasourcex/util/SimpleDataSourceFactory.kt

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
package com.caplin.integration.datasourcex.util
22

33
import com.caplin.datasource.DataSource
4-
import com.caplin.datasource.messaging.json.JacksonJsonHandler
54
import com.caplin.datasource.messaging.json.JsonHandler
65
import com.caplin.integration.datasourcex.util.SimpleDataSourceFactory.defaultJackson2JsonHandler
76
import com.caplin.integration.datasourcex.util.SimpleDataSourceFactory.defaultJackson2ObjectMapper
7+
import com.caplin.integration.datasourcex.util.serialization.jackson2.Jackson2JsonHandler
88
import com.caplin.integration.datasourcex.util.serialization.jackson2.registerDataSourceModule
9+
import com.caplin.integration.datasourcex.util.serialization.jackson3.Jackson3JsonHandler
10+
import com.caplin.integration.datasourcex.util.serialization.jackson3.addDataSourceModule
911
import com.fasterxml.jackson.databind.ObjectMapper
1012
import com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
1113
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
1214
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
1315
import java.nio.file.Files
1416
import java.util.logging.Logger
17+
import tools.jackson.databind.ObjectMapper as Jackson3ObjectMapper
18+
import tools.jackson.module.kotlin.jacksonMapperBuilder
1519

1620
/**
1721
* A factory for creating [DataSource] instances from simplified configurations. Allows easy setup
@@ -34,15 +38,25 @@ object SimpleDataSourceFactory {
3438
.registerDataSourceModule()
3539

3640
@JvmStatic
37-
fun createJackson2JsonHandler(objectMapper: ObjectMapper): JacksonJsonHandler =
38-
JacksonJsonHandler(
39-
Logger.getLogger(JacksonJsonHandler::class.qualifiedName),
40-
objectMapper,
41-
)
41+
fun createJackson2JsonHandler(objectMapper: ObjectMapper): Jackson2JsonHandler =
42+
Jackson2JsonHandler(objectMapper)
4243

43-
val defaultJackson2JsonHandler: JacksonJsonHandler =
44+
val defaultJackson2JsonHandler: Jackson2JsonHandler =
4445
createJackson2JsonHandler(defaultJackson2ObjectMapper)
4546

47+
// Lazy so the Jackson 3 classes (a compileOnly dependency) are only loaded if actually used.
48+
val defaultJackson3ObjectMapper: Jackson3ObjectMapper by lazy {
49+
jacksonMapperBuilder().addDataSourceModule().build()
50+
}
51+
52+
@JvmStatic
53+
fun createJackson3JsonHandler(objectMapper: Jackson3ObjectMapper): Jackson3JsonHandler =
54+
Jackson3JsonHandler(objectMapper)
55+
56+
val defaultJackson3JsonHandler: Jackson3JsonHandler by lazy {
57+
createJackson3JsonHandler(defaultJackson3ObjectMapper)
58+
}
59+
4660
/**
4761
* Creates a data source based on the given simple configuration.
4862
*
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.caplin.integration.datasourcex.util.serialization.jackson2
2+
3+
import com.caplin.datasource.messaging.json.JsonHandler
4+
import com.fasterxml.jackson.databind.JsonNode
5+
import com.fasterxml.jackson.databind.ObjectMapper
6+
import com.flipkart.zjsonpatch.JsonDiff
7+
import com.flipkart.zjsonpatch.JsonPatch
8+
9+
/**
10+
* A Jackson 2 [JsonHandler] backed by an [ObjectMapper], using zjsonpatch for RFC 6902 diff/patch.
11+
*/
12+
public class Jackson2JsonHandler(private val objectMapper: ObjectMapper) : JsonHandler<JsonNode> {
13+
14+
override fun toJsonTree(pojo: Any?): JsonNode = objectMapper.valueToTree(pojo)
15+
16+
override fun toObject(jsonTree: JsonNode, userType: Class<*>): Any? =
17+
objectMapper.treeToValue(jsonTree, userType)
18+
19+
override fun parse(jsonText: String): JsonNode = objectMapper.readTree(jsonText)
20+
21+
override fun format(jsonTree: JsonNode): String = objectMapper.writeValueAsString(jsonTree)
22+
23+
override fun diff(source: JsonNode, target: JsonNode): JsonNode = JsonDiff.asJson(source, target)
24+
25+
override fun patch(source: JsonNode, jsonPatch: JsonNode): JsonNode =
26+
JsonPatch.apply(jsonPatch, source)
27+
}

0 commit comments

Comments
 (0)