Skip to content

Commit 546ae15

Browse files
dmitrysulmansdeleuze
authored andcommitted
Support JSON list deserialization in KotlinSerializationStringDecoder
See gh-36597 Signed-off-by: Dmitry Sulman <dmitry.sulman@gmail.com>
1 parent 78c32aa commit 546ae15

4 files changed

Lines changed: 60 additions & 9 deletions

File tree

spring-test/spring-test.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
description = "Spring TestContext Framework"
22

33
apply plugin: "kotlin"
4+
apply plugin: "kotlinx-serialization"
45

56
dependencies {
67
api(project(":spring-core"))
@@ -81,6 +82,7 @@ dependencies {
8182
testImplementation("org.hibernate.orm:hibernate-core")
8283
testImplementation("org.hibernate.validator:hibernate-validator")
8384
testImplementation("org.hsqldb:hsqldb")
85+
testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json")
8486
testImplementation("org.junit.platform:junit-platform-testkit")
8587
testImplementation("tools.jackson.core:jackson-databind")
8688
testRuntimeOnly("com.sun.xml.bind:jaxb-core")
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.springframework.test.web.reactive.server
2+
3+
import kotlinx.serialization.Serializable
4+
import org.junit.jupiter.api.Test
5+
import org.springframework.http.MediaType
6+
import org.springframework.http.codec.json.KotlinSerializationJsonDecoder
7+
import org.springframework.web.bind.annotation.GetMapping
8+
import org.springframework.web.bind.annotation.RestController
9+
10+
class WebTestClientKotlinTests {
11+
12+
@Test
13+
fun expectBodyListKotlinSerialization() {
14+
val client = WebTestClient.bindToController(TestController::class.java)
15+
.configureClient()
16+
.codecs {
17+
it.registerDefaults(false)
18+
it.customCodecs().register(KotlinSerializationJsonDecoder())
19+
}.build()
20+
21+
client.get().uri("/test")
22+
.accept(MediaType.APPLICATION_JSON)
23+
.exchangeSuccessfully()
24+
.expectBodyList<Response>()
25+
.hasSize(2)
26+
.contains(Response("Hello"), Response("World"))
27+
}
28+
29+
@Serializable
30+
data class Response(val message: String)
31+
32+
@RestController
33+
class TestController {
34+
@GetMapping("test")
35+
fun test(): List<Response> = listOf(Response("Hello"), Response("World"))
36+
}
37+
}

spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationStringDecoder.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -116,23 +116,21 @@ public List<MimeType> getDecodableMimeTypes(ResolvableType targetType) {
116116
}
117117

118118
@Override
119+
@SuppressWarnings("unchecked")
119120
public Flux<Object> decode(Publisher<DataBuffer> inputStream, ResolvableType elementType,
120121
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
121122
return Flux.defer(() -> {
122123
KSerializer<Object> serializer = serializer(elementType);
123-
if (serializer == null) {
124+
KSerializer<Object> listSerializer = serializer(ResolvableType.forClassWithGenerics(List.class, elementType));
125+
if (serializer == null || listSerializer == null) {
124126
return Mono.error(new DecodingException("Could not find KSerializer for " + elementType));
125127
}
126128
return this.stringDecoder
127129
.decode(inputStream, elementType, mimeType, hints)
128-
.handle((string, sink) -> {
129-
try {
130-
sink.next(format().decodeFromString(serializer, string));
131-
}
132-
catch (IllegalArgumentException ex) {
133-
sink.error(processException(ex));
134-
}
135-
});
130+
.flatMapIterable(string -> string.startsWith("[") ?
131+
(List<Object>) format().decodeFromString(listSerializer, string) :
132+
List.of(format().decodeFromString(serializer, string)))
133+
.onErrorMap(IllegalArgumentException.class, this::processException);
136134
});
137135
}
138136

spring-web/src/test/kotlin/org/springframework/http/codec/json/KotlinSerializationJsonDecoderTests.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,20 @@ class KotlinSerializationJsonDecoderTests : AbstractDecoderTests<KotlinSerializa
197197
}, null, null)
198198
}
199199

200+
@Test
201+
fun decodeJsonArrayToFlux() {
202+
val input = Flux.concat(
203+
stringBuffer("[{\"bar\":\"b1\",\"foo\":\"f1\"},"),
204+
stringBuffer("{\"bar\":\"b2\",\"foo\":\"f2\"}]"))
205+
206+
testDecodeAll(input, ResolvableType.forClass(Pojo::class.java), {
207+
it.expectNext(Pojo("f1", "b1"))
208+
.expectNext(Pojo("f2", "b2"))
209+
.expectComplete()
210+
.verify()
211+
}, null, null)
212+
}
213+
200214
@Test
201215
override fun decodeToMono() {
202216
val input = Flux.concat(

0 commit comments

Comments
 (0)