diff --git a/spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationSupport.java b/spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationSupport.java index 53b7b804b61..7c8d57d1159 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationSupport.java +++ b/spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationSupport.java @@ -132,24 +132,25 @@ public abstract class KotlinSerializationSupport { Assert.notNull(method, "Method must not be null"); if (KotlinDetector.isKotlinType(method.getDeclaringClass())) { KFunction function = ReflectJvmMapping.getKotlinFunction(method); - Assert.notNull(function, "Kotlin function must not be null"); - KType type = (parameter.getParameterIndex() == -1 ? function.getReturnType() : - KCallables.getValueParameters(function).get(parameter.getParameterIndex()).getType()); - KSerializer serializer = this.kTypeSerializerCache.get(type); - if (serializer == null) { - try { - serializer = SerializersKt.serializerOrNull(this.format.getSerializersModule(), type); - } - catch (IllegalArgumentException ignored) { - } - if (serializer != null) { - if (hasPolymorphism(serializer.getDescriptor(), new HashSet<>())) { - return null; + if (function != null) { + KType type = (parameter.getParameterIndex() == -1 ? function.getReturnType() : + KCallables.getValueParameters(function).get(parameter.getParameterIndex()).getType()); + KSerializer serializer = this.kTypeSerializerCache.get(type); + if (serializer == null) { + try { + serializer = SerializersKt.serializerOrNull(this.format.getSerializersModule(), type); + } + catch (IllegalArgumentException ignored) { + } + if (serializer != null) { + if (hasPolymorphism(serializer.getDescriptor(), new HashSet<>())) { + return null; + } + this.kTypeSerializerCache.put(type, serializer); } - this.kTypeSerializerCache.put(type, serializer); } + return serializer; } - return serializer; } } Type type = resolvableType.getType(); diff --git a/spring-web/src/main/java/org/springframework/http/converter/AbstractKotlinSerializationHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/AbstractKotlinSerializationHttpMessageConverter.java index a6f75e27b54..ca0c65695a3 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/AbstractKotlinSerializationHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/AbstractKotlinSerializationHttpMessageConverter.java @@ -149,24 +149,25 @@ public abstract class AbstractKotlinSerializationHttpMessageConverter function = ReflectJvmMapping.getKotlinFunction(method); - Assert.notNull(function, "Kotlin function must not be null"); - KType type = (parameter.getParameterIndex() == -1 ? function.getReturnType() : - KCallables.getValueParameters(function).get(parameter.getParameterIndex()).getType()); - KSerializer serializer = this.kTypeSerializerCache.get(type); - if (serializer == null) { - try { - serializer = SerializersKt.serializerOrNull(this.format.getSerializersModule(), type); - } - catch (IllegalArgumentException ignored) { - } - if (serializer != null) { - if (hasPolymorphism(serializer.getDescriptor(), new HashSet<>())) { - return null; + if (function != null) { + KType type = (parameter.getParameterIndex() == -1 ? function.getReturnType() : + KCallables.getValueParameters(function).get(parameter.getParameterIndex()).getType()); + KSerializer serializer = this.kTypeSerializerCache.get(type); + if (serializer == null) { + try { + serializer = SerializersKt.serializerOrNull(this.format.getSerializersModule(), type); + } + catch (IllegalArgumentException ignored) { + } + if (serializer != null) { + if (hasPolymorphism(serializer.getDescriptor(), new HashSet<>())) { + return null; + } + this.kTypeSerializerCache.put(type, serializer); } - this.kTypeSerializerCache.put(type, serializer); } + return serializer; } - return serializer; } } Type type = resolvableType.getType(); diff --git a/spring-web/src/test/kotlin/org/springframework/http/codec/json/KotlinSerializationJsonEncoderTests.kt b/spring-web/src/test/kotlin/org/springframework/http/codec/json/KotlinSerializationJsonEncoderTests.kt index 86c89a0d160..d5c92147929 100644 --- a/spring-web/src/test/kotlin/org/springframework/http/codec/json/KotlinSerializationJsonEncoderTests.kt +++ b/spring-web/src/test/kotlin/org/springframework/http/codec/json/KotlinSerializationJsonEncoderTests.kt @@ -145,10 +145,24 @@ class KotlinSerializationJsonEncoderTests : AbstractEncoderTests) = map + val value: Int + get() = 42 + } diff --git a/spring-web/src/test/kotlin/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverterTests.kt b/spring-web/src/test/kotlin/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverterTests.kt index 4c3b9b5dd0b..a495d9136b5 100644 --- a/spring-web/src/test/kotlin/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverterTests.kt +++ b/spring-web/src/test/kotlin/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverterTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 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. @@ -16,18 +16,11 @@ package org.springframework.http.converter.json -import java.lang.reflect.ParameterizedType -import java.lang.reflect.Type -import java.nio.charset.StandardCharsets - import kotlinx.serialization.Serializable import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.jupiter.api.Test import org.springframework.core.MethodParameter -import kotlin.reflect.javaType -import kotlin.reflect.typeOf - import org.springframework.core.Ordered import org.springframework.core.ResolvableType import org.springframework.http.MediaType @@ -35,8 +28,12 @@ import org.springframework.http.converter.HttpMessageNotReadableException import org.springframework.http.customJson import org.springframework.web.testfixture.http.MockHttpInputMessage import org.springframework.web.testfixture.http.MockHttpOutputMessage +import java.lang.reflect.ParameterizedType import java.math.BigDecimal +import java.nio.charset.StandardCharsets +import kotlin.reflect.javaType import kotlin.reflect.jvm.javaMethod +import kotlin.reflect.typeOf /** * Tests for the JSON conversion using kotlinx.serialization. @@ -395,6 +392,20 @@ class KotlinSerializationJsonHttpMessageConverterTests { assertThat(result).isEqualTo(expectedJson) } + @Test + fun writeProperty() { + val outputMessage = MockHttpOutputMessage() + val method = this::class.java.getDeclaredMethod("getValue") + val methodParameter = MethodParameter.forExecutable(method, -1) + + this.converter.write(value, ResolvableType.forMethodParameter(methodParameter), null, outputMessage, null) + val result = outputMessage.getBodyAsString(StandardCharsets.UTF_8) + + @Suppress("DEPRECATION") + assertThat(outputMessage.headers.asMultiValueMap()).containsEntry("Content-Type", listOf("application/json")) + assertThat(result).isEqualTo("42") + } + @Serializable @Suppress("ArrayInDataClass") @@ -420,4 +431,7 @@ class KotlinSerializationJsonHttpMessageConverterTests { fun handleMapWithNullable(map: Map) = map + val value: Int + get() = 42 + }