diff --git a/spring-test/src/test/kotlin/org/springframework/test/web/reactive/server/WebTestClientKotlinTests.kt b/spring-test/src/test/kotlin/org/springframework/test/web/reactive/server/WebTestClientKotlinTests.kt index 69353f358d1..8e22ea41bbd 100644 --- a/spring-test/src/test/kotlin/org/springframework/test/web/reactive/server/WebTestClientKotlinTests.kt +++ b/spring-test/src/test/kotlin/org/springframework/test/web/reactive/server/WebTestClientKotlinTests.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2002-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.test.web.reactive.server import kotlinx.serialization.Serializable @@ -34,4 +50,4 @@ class WebTestClientKotlinTests { @GetMapping("test") fun test(): List = listOf(Response("Hello"), Response("World")) } -} \ No newline at end of file +} diff --git a/spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationStringDecoder.java b/spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationStringDecoder.java index e234ca4c688..69fb226442e 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationStringDecoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationStringDecoder.java @@ -55,7 +55,7 @@ public abstract class KotlinSerializationStringDecoder e implements Decoder { // String decoding needed for now, see https://github.com/Kotlin/kotlinx.serialization/issues/204 for more details - private final StringDecoder stringDecoder = StringDecoder.allMimeTypes(StringDecoder.DEFAULT_DELIMITERS, false); + protected final StringDecoder stringDecoder = StringDecoder.allMimeTypes(StringDecoder.DEFAULT_DELIMITERS, false); /** @@ -116,21 +116,23 @@ public abstract class KotlinSerializationStringDecoder e } @Override - @SuppressWarnings("unchecked") public Flux decode(Publisher inputStream, ResolvableType elementType, @Nullable MimeType mimeType, @Nullable Map hints) { return Flux.defer(() -> { KSerializer serializer = serializer(elementType); - KSerializer listSerializer = serializer(ResolvableType.forClassWithGenerics(List.class, elementType)); - if (serializer == null || listSerializer == null) { + if (serializer == null) { return Mono.error(new DecodingException("Could not find KSerializer for " + elementType)); } return this.stringDecoder .decode(inputStream, elementType, mimeType, hints) - .flatMapIterable(string -> string.startsWith("[") ? - (List) format().decodeFromString(listSerializer, string) : - List.of(format().decodeFromString(serializer, string))) - .onErrorMap(IllegalArgumentException.class, this::processException); + .handle((string, sink) -> { + try { + sink.next(format().decodeFromString(serializer, string)); + } + catch (IllegalArgumentException ex) { + sink.error(processException(ex)); + } + }); }); } @@ -156,7 +158,7 @@ public abstract class KotlinSerializationStringDecoder e }); } - private CodecException processException(IllegalArgumentException ex) { + protected CodecException processException(IllegalArgumentException ex) { return new DecodingException("Decoding error: " + ex.getMessage(), ex); } diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/KotlinSerializationJsonDecoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/KotlinSerializationJsonDecoder.java index 227f320987c..7198e1992b5 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/KotlinSerializationJsonDecoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/KotlinSerializationJsonDecoder.java @@ -16,11 +16,22 @@ package org.springframework.http.codec.json; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.function.Predicate; +import kotlinx.serialization.KSerializer; +import kotlinx.serialization.builtins.BuiltinSerializersKt; import kotlinx.serialization.json.Json; +import org.jspecify.annotations.Nullable; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; import org.springframework.core.ResolvableType; +import org.springframework.core.codec.DecodingException; +import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.MediaType; import org.springframework.http.codec.KotlinSerializationStringDecoder; import org.springframework.util.MimeType; @@ -95,4 +106,37 @@ public class KotlinSerializationJsonDecoder extends KotlinSerializationStringDec super(json, typePredicate, DEFAULT_JSON_MIME_TYPES); } + @Override + public Flux decode(Publisher inputStream, ResolvableType elementType, + @Nullable MimeType mimeType, @Nullable Map hints) { + return Flux.defer(() -> { + KSerializer serializer = serializer(elementType); + if (serializer == null) { + return Mono.error(new DecodingException("Could not find KSerializer for " + elementType)); + } + return this.stringDecoder + .decode(inputStream, elementType, mimeType, hints) + .switchOnFirst((signal, flux) -> { + if (signal.hasValue()) { + String value = Objects.requireNonNull(signal.get()); + if (value.stripLeading().startsWith("[") && !List.class.isAssignableFrom(elementType.toClass())) { + KSerializer> listSerializer = BuiltinSerializersKt.ListSerializer(serializer); + return flux + .flatMapIterable(string -> format().decodeFromString(listSerializer, string)) + .onErrorMap(IllegalArgumentException.class, this::processException); + } + return flux.handle((string, sink) -> { + try { + sink.next(format().decodeFromString(serializer, string)); + } + catch (IllegalArgumentException ex) { + sink.error(processException(ex)); + } + }); + } + return flux; + }); + }); + } + } diff --git a/spring-web/src/test/kotlin/org/springframework/http/codec/json/KotlinSerializationJsonDecoderTests.kt b/spring-web/src/test/kotlin/org/springframework/http/codec/json/KotlinSerializationJsonDecoderTests.kt index f2e6a93800f..ce53bb9a0ac 100644 --- a/spring-web/src/test/kotlin/org/springframework/http/codec/json/KotlinSerializationJsonDecoderTests.kt +++ b/spring-web/src/test/kotlin/org/springframework/http/codec/json/KotlinSerializationJsonDecoderTests.kt @@ -211,6 +211,20 @@ class KotlinSerializationJsonDecoderTests : AbstractDecoderTests