Skip to content

Commit 45c38c1

Browse files
authored
Merge pull request #31408 from quarkusio/dependabot/maven/org.jetbrains.kotlinx-kotlinx-serialization-json-1.5.0
Bump kotlinx-serialization-json from 1.4.1 to 1.5.0
2 parents ca20858 + f780649 commit 45c38c1

File tree

10 files changed

+135
-15
lines changed

10 files changed

+135
-15
lines changed

bom/application/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@
160160
<azure-functions-java-spi.version>1.0.0</azure-functions-java-spi.version>
161161
<kotlin.version>1.8.10</kotlin.version>
162162
<kotlin.coroutine.version>1.6.4</kotlin.coroutine.version>
163-
<kotlin-serialization.version>1.4.1</kotlin-serialization.version>
163+
<kotlin-serialization.version>1.5.0</kotlin-serialization.version>
164164
<dekorate.version>3.4.1</dekorate.version> <!-- Please check with Java Operator SDK team before updating -->
165165
<maven-invoker.version>3.2.0</maven-invoker.version>
166166
<awaitility.version>4.2.0</awaitility.version>

extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
<groupId>org.jetbrains.kotlinx</groupId>
2727
<artifactId>kotlinx-serialization-json</artifactId>
2828
</dependency>
29+
<dependency>
30+
<groupId>org.jetbrains.kotlin</groupId>
31+
<artifactId>kotlin-reflect</artifactId>
32+
</dependency>
2933
<dependency>
3034
<groupId>io.quarkus</groupId>
3135
<artifactId>quarkus-junit5-internal</artifactId>

extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/java/io/quarkus/resteasy/reactive/kotlin/serialization/common/runtime/JsonConfig.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.quarkus.resteasy.reactive.kotlin.serialization.common.runtime;
22

3+
import java.util.Optional;
34
import java.util.StringJoiner;
45

56
import io.quarkus.runtime.annotations.ConfigGroup;
@@ -100,6 +101,28 @@ public class JsonConfig {
100101
@ConfigItem(defaultValue = "false")
101102
public boolean useArrayPolymorphism = false;
102103

104+
/**
105+
* Specifies the {@code JsonNamingStrategy} that should be used for all properties in classes for serialization and
106+
* deserialization.
107+
* This strategy is applied for all entities that have {@code StructureKind.CLASS}.
108+
* <p>
109+
* <p>
110+
* {@code null} by default.
111+
* <p>
112+
* <p>
113+
* This element can be one of two things:
114+
* <ol>
115+
* <li>the fully qualified class name of a type implements the {@code NamingStrategy} interface and has a no-arg
116+
* constructor</li>
117+
* <li>a value in the form {@code NamingStrategy.SnakeCase} which refers to built-in values provided by the kotlin
118+
* serialization
119+
* library itself.
120+
* </li>
121+
* </ol>
122+
*/
123+
@ConfigItem(name = "naming-strategy")
124+
public Optional<String> namingStrategy;
125+
103126
@Override
104127
public String toString() {
105128
return new StringJoiner(", ", JsonConfig.class.getSimpleName() + "[", "]")

extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/kotlin/io/quarkus/resteasy/reactive/kotlin/serialization/common/runtime/JsonProducer.kt

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@ import jakarta.enterprise.inject.Produces
77
import jakarta.inject.Singleton
88
import kotlinx.serialization.ExperimentalSerializationApi
99
import kotlinx.serialization.json.Json
10+
import kotlinx.serialization.json.JsonBuilder
11+
import kotlinx.serialization.json.JsonNamingStrategy
12+
import kotlinx.serialization.json.JsonNamingStrategy.Builtins
13+
import java.lang.Thread
14+
import kotlin.reflect.KMutableProperty1
15+
import kotlin.reflect.full.memberProperties
16+
import kotlin.reflect.jvm.isAccessible
1017

1118
@Singleton
1219
class JsonProducer {
13-
1420
@ExperimentalSerializationApi
1521
@Singleton
1622
@Produces
@@ -29,12 +35,65 @@ class JsonProducer {
2935
useAlternativeNames = configuration.json.useAlternativeNames
3036
useArrayPolymorphism = configuration.json.useArrayPolymorphism
3137

38+
configuration.json.namingStrategy.ifPresent { strategy ->
39+
loadStrategy(this, strategy, this@JsonProducer)
40+
}
3241
val sortedCustomizers = sortCustomizersInDescendingPriorityOrder(customizers)
3342
for (customizer in sortedCustomizers) {
3443
customizer.customize(this)
3544
}
3645
}
3746

47+
@ExperimentalSerializationApi
48+
private fun loadStrategy(
49+
jsonBuilder: JsonBuilder,
50+
strategy: String,
51+
jsonProducer: JsonProducer
52+
) {
53+
val strategyProperty: KMutableProperty1<JsonBuilder, JsonNamingStrategy> = (
54+
JsonBuilder::class.memberProperties
55+
.find { member -> member.name == "namingStrategy" }
56+
?: throw ReflectiveOperationException("Could not find the namingStrategy property on JsonBuilder")
57+
) as KMutableProperty1<JsonBuilder, JsonNamingStrategy>
58+
strategyProperty.isAccessible = true
59+
60+
strategyProperty.set(
61+
jsonBuilder,
62+
if (strategy.startsWith("JsonNamingStrategy")) {
63+
jsonProducer.extractBuiltIn(strategy)
64+
} else {
65+
jsonProducer.loadStrategyClass(strategy)
66+
}
67+
)
68+
}
69+
70+
@ExperimentalSerializationApi
71+
private fun loadStrategyClass(
72+
strategy: String
73+
): JsonNamingStrategy {
74+
try {
75+
val strategyClass: Class<JsonNamingStrategy> = Thread.currentThread().contextClassLoader.loadClass(strategy) as Class<JsonNamingStrategy>
76+
val constructor = strategyClass.constructors
77+
.find { it.parameterCount == 0 }
78+
?: throw ReflectiveOperationException("No no-arg constructor found on $strategy")
79+
return constructor.newInstance() as JsonNamingStrategy
80+
} catch (e: ReflectiveOperationException) {
81+
throw IllegalArgumentException("Error loading naming strategy: ${strategy.substringAfter('.')}", e)
82+
}
83+
}
84+
85+
@ExperimentalSerializationApi
86+
private fun extractBuiltIn(
87+
strategy: String
88+
): JsonNamingStrategy {
89+
val kClass = Builtins::class
90+
val property = kClass.memberProperties.find { property ->
91+
property.name == strategy.substringAfter('.')
92+
} ?: throw IllegalArgumentException("Unknown naming strategy provided: ${strategy.substringAfter('.')}")
93+
94+
return property.get(JsonNamingStrategy) as JsonNamingStrategy
95+
}
96+
3897
private fun sortCustomizersInDescendingPriorityOrder(
3998
customizers: Iterable<JsonBuilderCustomizer>
4099
): List<JsonBuilderCustomizer> {

integration-tests/kotlin-serialization/src/main/kotlin/io/quarkus/it/kotser/GreetingResource.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class GreetingResource {
6262
@Consumes(MediaType.APPLICATION_JSON)
6363
@Produces(MediaType.APPLICATION_JSON)
6464
fun marry(person: Person): Person {
65-
return Person(person.name.substringBefore(" ") + " Halpert")
65+
return Person(person.fullName.substringBefore(" ") + " Halpert")
6666
}
6767

6868
@GET
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.quarkus.it.kotser
2+
3+
import kotlinx.serialization.ExperimentalSerializationApi
4+
import kotlinx.serialization.descriptors.SerialDescriptor
5+
import kotlinx.serialization.json.JsonNamingStrategy
6+
7+
@OptIn(ExperimentalSerializationApi::class)
8+
class TitleCase : JsonNamingStrategy {
9+
override fun serialNameForJson(descriptor: SerialDescriptor, elementIndex: Int, serialName: String): String {
10+
return serialName[0].uppercase() + serialName.substring(1)
11+
}
12+
}

integration-tests/kotlin-serialization/src/main/kotlin/io/quarkus/it/kotser/model/Person.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package io.quarkus.it.kotser.model
33
import kotlinx.serialization.Serializable
44

55
@Serializable
6-
data class Person(var name: String, var defaulted: String = "hi there!") {
6+
data class Person(var fullName: String, var defaulted: String = "hi there!") {
77
override fun toString(): String {
88
TODO("this shouldn't get called. a proper serialization should be invoked.")
99
}

integration-tests/kotlin-serialization/src/main/kotlin/io/quarkus/it/kotser/model/Person2.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package io.quarkus.it.kotser.model
33
import kotlinx.serialization.Serializable
44

55
@Serializable
6-
data class Person2(var name: String, var defaulted: String = "hey") {
6+
data class Person2(var fullName: String, var defaulted: String = "hey") {
77
override fun toString(): String {
88
TODO("this shouldn't get called. a proper serialization should be invoked.")
99
}
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
quarkus.kotlin-serialization.json.encode-defaults=true
1+
quarkus.kotlin-serialization.json.encode-defaults=true
2+
#quarkus.kotlin-serialization.json.naming-strategy=io.quarkus.it.kotser.TitleCase
3+
#quarkus.kotlin-serialization.json.naming-strategy=JsonNamingStrategy.SnakeCase

integration-tests/kotlin-serialization/src/test/kotlin/io/quarkus/it/kotser/ResourceTest.kt

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,36 @@ import jakarta.ws.rs.core.MediaType
99
import org.hamcrest.CoreMatchers
1010
import org.hamcrest.CoreMatchers.`is`
1111
import org.junit.jupiter.api.Test
12+
import java.util.Properties
1213

1314
@QuarkusTest
1415
open class ResourceTest {
16+
17+
val nameField: String
18+
var defaulted = "defaulted"
19+
20+
init {
21+
val properties = Properties()
22+
properties.load(javaClass.getResourceAsStream("/application.properties"))
23+
val strategy: String? = properties.get("quarkus.kotlin-serialization.json.naming-strategy") as String?
24+
when (strategy) {
25+
"JsonNamingStrategy.SnakeCase" -> nameField = "full_name"
26+
TitleCase::class.qualifiedName -> {
27+
nameField = "FullName"
28+
defaulted = "Defaulted"
29+
}
30+
null -> nameField = "fullName"
31+
else -> throw IllegalArgumentException("unknown strategy: $strategy")
32+
}
33+
}
34+
1535
@Test
1636
fun testGetFlow() {
1737
When {
1838
get("/flow")
1939
} Then {
2040
statusCode(200)
21-
body(`is`("""[{"name":"Jim Halpert","defaulted":"hi there!"}]"""))
41+
body(`is`("""[{"$nameField":"Jim Halpert","$defaulted":"hi there!"}]"""))
2242
}
2343
}
2444

@@ -28,7 +48,7 @@ open class ResourceTest {
2848
get("/")
2949
} Then {
3050
statusCode(200)
31-
body(`is`("""{"name":"Jim Halpert","defaulted":"hi there!"}"""))
51+
body(`is`("""{"$nameField":"Jim Halpert","$defaulted":"hi there!"}"""))
3252
}
3353
}
3454

@@ -38,7 +58,7 @@ open class ResourceTest {
3858
get("/restResponse")
3959
} Then {
4060
statusCode(200)
41-
body(`is`("""{"name":"Jim Halpert","defaulted":"hi there!"}"""))
61+
body(`is`("""{"$nameField":"Jim Halpert","$defaulted":"hi there!"}"""))
4262
}
4363
}
4464

@@ -48,7 +68,7 @@ open class ResourceTest {
4868
get("/restResponseList")
4969
} Then {
5070
statusCode(200)
51-
body(`is`("""[{"name":"Jim Halpert","defaulted":"hi there!"}]"""))
71+
body(`is`("""[{"$nameField":"Jim Halpert","$defaulted":"hi there!"}]"""))
5272
}
5373
}
5474

@@ -58,7 +78,7 @@ open class ResourceTest {
5878
get("/unknownType")
5979
} Then {
6080
statusCode(200)
61-
body(`is`("""{"name":"Foo Bar","defaulted":"hey"}"""))
81+
body(`is`("""{"$nameField":"Foo Bar","$defaulted":"hey"}"""))
6282
}
6383
}
6484

@@ -68,7 +88,7 @@ open class ResourceTest {
6888
get("/suspend")
6989
} Then {
7090
statusCode(200)
71-
body(`is`("""{"name":"Jim Halpert","defaulted":"hi there!"}"""))
91+
body(`is`("""{"$nameField":"Jim Halpert","$defaulted":"hi there!"}"""))
7292
}
7393
}
7494

@@ -78,20 +98,20 @@ open class ResourceTest {
7898
get("/suspendList")
7999
} Then {
80100
statusCode(200)
81-
body(`is`("""[{"name":"Jim Halpert","defaulted":"hi there!"}]"""))
101+
body(`is`("""[{"$nameField":"Jim Halpert","$defaulted":"hi there!"}]"""))
82102
}
83103
}
84104

85105
@Test
86106
fun testPost() {
87107
Given {
88-
body("""{ "name": "Pam Beasley" }""")
108+
body("""{ "$nameField": "Pam Beasley" }""")
89109
contentType(JSON)
90110
} When {
91111
post("/")
92112
} Then {
93113
statusCode(200)
94-
body(`is`("""{"name":"Pam Halpert","defaulted":"hi there!"}"""))
114+
body(`is`("""{"$nameField":"Pam Halpert","$defaulted":"hi there!"}"""))
95115
}
96116
}
97117

0 commit comments

Comments
 (0)