Browse Source

Support open polymorphism with Kotlin Serialization

This commit introduces open polymorphism support with Kotlin
Serialization in HTTP converters and codecs as a follow-up of gh-34410
which considers Kotlin Serialization as a Jackson/Gson/Jsonb equivalent
(it is not anymore configured before Jackson).

Closes gh-34433
pull/34440/head
Sébastien Deleuze 1 year ago
parent
commit
6bdb9bb950
  1. 6
      spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationBinaryDecoder.java
  2. 6
      spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationBinaryEncoder.java
  3. 6
      spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationStringDecoder.java
  4. 6
      spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationStringEncoder.java
  5. 31
      spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationSupport.java
  6. 10
      spring-web/src/main/java/org/springframework/http/codec/cbor/KotlinSerializationCborDecoder.java
  7. 10
      spring-web/src/main/java/org/springframework/http/codec/cbor/KotlinSerializationCborEncoder.java
  8. 10
      spring-web/src/main/java/org/springframework/http/codec/json/KotlinSerializationJsonDecoder.java
  9. 10
      spring-web/src/main/java/org/springframework/http/codec/json/KotlinSerializationJsonEncoder.java
  10. 10
      spring-web/src/main/java/org/springframework/http/codec/protobuf/KotlinSerializationProtobufDecoder.java
  11. 10
      spring-web/src/main/java/org/springframework/http/codec/protobuf/KotlinSerializationProtobufEncoder.java
  12. 30
      spring-web/src/main/java/org/springframework/http/converter/AbstractKotlinSerializationHttpMessageConverter.java
  13. 6
      spring-web/src/main/java/org/springframework/http/converter/KotlinSerializationBinaryHttpMessageConverter.java
  14. 6
      spring-web/src/main/java/org/springframework/http/converter/KotlinSerializationStringHttpMessageConverter.java
  15. 10
      spring-web/src/main/java/org/springframework/http/converter/cbor/KotlinSerializationCborHttpMessageConverter.java
  16. 10
      spring-web/src/main/java/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverter.java
  17. 10
      spring-web/src/main/java/org/springframework/http/converter/protobuf/KotlinSerializationProtobufHttpMessageConverter.java
  18. 21
      spring-web/src/test/kotlin/org/springframework/http/codec/cbor/KotlinSerializationCborDecoderTests.kt
  19. 14
      spring-web/src/test/kotlin/org/springframework/http/codec/cbor/KotlinSerializationCborEncoderTests.kt
  20. 50
      spring-web/src/test/kotlin/org/springframework/http/codec/json/KotlinSerializationJsonDecoderTests.kt
  21. 48
      spring-web/src/test/kotlin/org/springframework/http/codec/json/KotlinSerializationJsonEncoderTests.kt
  22. 20
      spring-web/src/test/kotlin/org/springframework/http/codec/protobuf/KotlinSerializationProtobufDecoderTests.kt
  23. 13
      spring-web/src/test/kotlin/org/springframework/http/codec/protobuf/KotlinSerializationProtobufEncoderTests.kt
  24. 28
      spring-web/src/test/kotlin/org/springframework/http/converter/cbor/KotlinSerializationCborHttpMessageConverterTests.kt
  25. 77
      spring-web/src/test/kotlin/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverterTests.kt
  26. 28
      spring-web/src/test/kotlin/org/springframework/http/converter/protobuf/KotlinSerializationProtobufHttpMessageConverterTests.kt

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -37,6 +37,10 @@ import org.springframework.util.MimeType;
* Abstract base class for {@link Decoder} implementations that defer to Kotlin * Abstract base class for {@link Decoder} implementations that defer to Kotlin
* {@linkplain BinaryFormat binary serializers}. * {@linkplain BinaryFormat binary serializers}.
* *
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
*
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Iain Henderson * @author Iain Henderson
* @author Arjen Poutsma * @author Arjen Poutsma

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -38,6 +38,10 @@ import org.springframework.util.MimeType;
* Abstract base class for {@link Encoder} implementations that defer to Kotlin * Abstract base class for {@link Encoder} implementations that defer to Kotlin
* {@linkplain BinaryFormat binary serializers}. * {@linkplain BinaryFormat binary serializers}.
* *
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
*
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Iain Henderson * @author Iain Henderson
* @author Arjen Poutsma * @author Arjen Poutsma

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -38,6 +38,10 @@ import org.springframework.util.MimeType;
* Abstract base class for {@link Decoder} implementations that defer to Kotlin * Abstract base class for {@link Decoder} implementations that defer to Kotlin
* {@linkplain StringFormat string serializers}. * {@linkplain StringFormat string serializers}.
* *
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
*
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Iain Henderson * @author Iain Henderson
* @author Arjen Poutsma * @author Arjen Poutsma

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -43,6 +43,10 @@ import org.springframework.util.MimeType;
* Abstract base class for {@link Encoder} implementations that defer to Kotlin * Abstract base class for {@link Encoder} implementations that defer to Kotlin
* {@linkplain StringFormat string serializers}. * {@linkplain StringFormat string serializers}.
* *
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
*
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Iain Henderson * @author Iain Henderson
* @author Arjen Poutsma * @author Arjen Poutsma

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

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,10 +19,8 @@ package org.springframework.http.codec;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import kotlin.reflect.KFunction; import kotlin.reflect.KFunction;
import kotlin.reflect.KType; import kotlin.reflect.KType;
@ -31,8 +29,6 @@ import kotlin.reflect.jvm.ReflectJvmMapping;
import kotlinx.serialization.KSerializer; import kotlinx.serialization.KSerializer;
import kotlinx.serialization.SerialFormat; import kotlinx.serialization.SerialFormat;
import kotlinx.serialization.SerializersKt; import kotlinx.serialization.SerializersKt;
import kotlinx.serialization.descriptors.PolymorphicKind;
import kotlinx.serialization.descriptors.SerialDescriptor;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.core.KotlinDetector; import org.springframework.core.KotlinDetector;
@ -46,6 +42,10 @@ import org.springframework.util.MimeType;
* Base class providing support methods for encoding and decoding with Kotlin * Base class providing support methods for encoding and decoding with Kotlin
* serialization. * serialization.
* *
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
*
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Iain Henderson * @author Iain Henderson
* @author Arjen Poutsma * @author Arjen Poutsma
@ -143,9 +143,6 @@ public abstract class KotlinSerializationSupport<T extends SerialFormat> {
catch (IllegalArgumentException ignored) { catch (IllegalArgumentException ignored) {
} }
if (serializer != null) { if (serializer != null) {
if (hasPolymorphism(serializer.getDescriptor(), new HashSet<>())) {
return null;
}
this.kTypeSerializerCache.put(type, serializer); this.kTypeSerializerCache.put(type, serializer);
} }
} }
@ -162,27 +159,9 @@ public abstract class KotlinSerializationSupport<T extends SerialFormat> {
catch (IllegalArgumentException ignored) { catch (IllegalArgumentException ignored) {
} }
if (serializer != null) { if (serializer != null) {
if (hasPolymorphism(serializer.getDescriptor(), new HashSet<>())) {
return null;
}
this.typeSerializerCache.put(type, serializer); this.typeSerializerCache.put(type, serializer);
} }
} }
return serializer; return serializer;
} }
private static boolean hasPolymorphism(SerialDescriptor descriptor, Set<String> alreadyProcessed) {
alreadyProcessed.add(descriptor.getSerialName());
if (descriptor.getKind().equals(PolymorphicKind.OPEN.INSTANCE)) {
return true;
}
for (int i = 0 ; i < descriptor.getElementsCount() ; i++) {
SerialDescriptor elementDescriptor = descriptor.getElementDescriptor(i);
if (!alreadyProcessed.contains(elementDescriptor.getSerialName()) && hasPolymorphism(elementDescriptor, alreadyProcessed)) {
return true;
}
}
return false;
}
} }

10
spring-web/src/main/java/org/springframework/http/codec/cbor/KotlinSerializationCborDecoder.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -24,12 +24,12 @@ import org.springframework.http.codec.KotlinSerializationBinaryDecoder;
/** /**
* Decode a byte stream into CBOR and convert to Objects with * Decode a byte stream into CBOR and convert to Objects with
* <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>. * <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>.
*
* <p>This decoder can be used to bind {@code @Serializable} Kotlin classes,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphic serialization</a>
* is not supported.
* It supports {@code application/cbor}. * It supports {@code application/cbor}.
* *
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
*
* <p>Decoding streams is not supported yet, see * <p>Decoding streams is not supported yet, see
* <a href="https://github.com/Kotlin/kotlinx.serialization/issues/1073">kotlinx.serialization/issues/1073</a> * <a href="https://github.com/Kotlin/kotlinx.serialization/issues/1073">kotlinx.serialization/issues/1073</a>
* related issue. * related issue.

10
spring-web/src/main/java/org/springframework/http/codec/cbor/KotlinSerializationCborEncoder.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -24,12 +24,12 @@ import org.springframework.http.codec.KotlinSerializationBinaryEncoder;
/** /**
* Encode from an {@code Object} stream to a byte stream of CBOR objects using * Encode from an {@code Object} stream to a byte stream of CBOR objects using
* <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>. * <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>.
*
* <p>This encoder can be used to bind {@code @Serializable} Kotlin classes,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphic serialization</a>
* is not supported.
* It supports {@code application/cbor}. * It supports {@code application/cbor}.
* *
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
*
* @author Iain Henderson * @author Iain Henderson
* @since 6.0 * @since 6.0
*/ */

10
spring-web/src/main/java/org/springframework/http/codec/json/KotlinSerializationJsonDecoder.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -24,13 +24,13 @@ import org.springframework.http.codec.KotlinSerializationStringDecoder;
/** /**
* Decode a byte stream into JSON and convert to Object's with * Decode a byte stream into JSON and convert to Object's with
* <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>. * <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>.
*
* <p>This decoder can be used to bind {@code @Serializable} Kotlin classes,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphic serialization</a>
* is not supported.
* It supports {@code application/json} and {@code application/*+json} with * It supports {@code application/json} and {@code application/*+json} with
* various character sets, {@code UTF-8} being the default. * various character sets, {@code UTF-8} being the default.
* *
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
*
* <p>Decoding streams is not supported yet, see * <p>Decoding streams is not supported yet, see
* <a href="https://github.com/Kotlin/kotlinx.serialization/issues/1073">kotlinx.serialization/issues/1073</a> * <a href="https://github.com/Kotlin/kotlinx.serialization/issues/1073">kotlinx.serialization/issues/1073</a>
* related issue. * related issue.

10
spring-web/src/main/java/org/springframework/http/codec/json/KotlinSerializationJsonEncoder.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -35,13 +35,13 @@ import org.springframework.util.MimeType;
/** /**
* Encode from an {@code Object} stream to a byte stream of JSON objects using * Encode from an {@code Object} stream to a byte stream of JSON objects using
* <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>. * <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>.
*
* <p>This encoder can be used to bind {@code @Serializable} Kotlin classes,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphic serialization</a>
* is not supported.
* It supports {@code application/json}, {@code application/x-ndjson} and {@code application/*+json} with * It supports {@code application/json}, {@code application/x-ndjson} and {@code application/*+json} with
* various character sets, {@code UTF-8} being the default. * various character sets, {@code UTF-8} being the default.
* *
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
*
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Iain Henderson * @author Iain Henderson
* @since 5.3 * @since 5.3

10
spring-web/src/main/java/org/springframework/http/codec/protobuf/KotlinSerializationProtobufDecoder.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -23,12 +23,12 @@ import org.springframework.http.codec.KotlinSerializationBinaryDecoder;
/** /**
* Decode a byte stream into a protocol Buffer and convert to Objects with * Decode a byte stream into a protocol Buffer and convert to Objects with
* <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>. * <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>.
*
* <p>This decoder can be used to bind {@code @Serializable} Kotlin classes,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphic serialization</a>
* is not supported.
* It supports {@code application/x-protobuf}, {@code application/octet-stream}, and {@code application/vnd.google.protobuf}. * It supports {@code application/x-protobuf}, {@code application/octet-stream}, and {@code application/vnd.google.protobuf}.
* *
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
*
* <p>Decoding streams is not supported yet, see * <p>Decoding streams is not supported yet, see
* <a href="https://github.com/Kotlin/kotlinx.serialization/issues/1073">kotlinx.serialization/issues/1073</a> * <a href="https://github.com/Kotlin/kotlinx.serialization/issues/1073">kotlinx.serialization/issues/1073</a>
* related issue. * related issue.

10
spring-web/src/main/java/org/springframework/http/codec/protobuf/KotlinSerializationProtobufEncoder.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -23,12 +23,12 @@ import org.springframework.http.codec.KotlinSerializationBinaryEncoder;
/** /**
* Decode a byte stream into a Protocol Buffer and convert to Objects with * Decode a byte stream into a Protocol Buffer and convert to Objects with
* <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>. * <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>.
*
* <p>This decoder can be used to bind {@code @Serializable} Kotlin classes,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphic serialization</a>
* is not supported.
* It supports {@code application/x-protobuf}, {@code application/octet-stream}, and {@code application/vnd.google.protobuf}. * It supports {@code application/x-protobuf}, {@code application/octet-stream}, and {@code application/vnd.google.protobuf}.
* *
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
*
* <p>Decoding streams is not supported yet, see * <p>Decoding streams is not supported yet, see
* <a href="https://github.com/Kotlin/kotlinx.serialization/issues/1073">kotlinx.serialization/issues/1073</a> * <a href="https://github.com/Kotlin/kotlinx.serialization/issues/1073">kotlinx.serialization/issues/1073</a>
* related issue. * related issue.

30
spring-web/src/main/java/org/springframework/http/converter/AbstractKotlinSerializationHttpMessageConverter.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,9 +19,7 @@ package org.springframework.http.converter;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set;
import kotlin.reflect.KFunction; import kotlin.reflect.KFunction;
import kotlin.reflect.KType; import kotlin.reflect.KType;
@ -30,8 +28,6 @@ import kotlin.reflect.jvm.ReflectJvmMapping;
import kotlinx.serialization.KSerializer; import kotlinx.serialization.KSerializer;
import kotlinx.serialization.SerialFormat; import kotlinx.serialization.SerialFormat;
import kotlinx.serialization.SerializersKt; import kotlinx.serialization.SerializersKt;
import kotlinx.serialization.descriptors.PolymorphicKind;
import kotlinx.serialization.descriptors.SerialDescriptor;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.core.KotlinDetector; import org.springframework.core.KotlinDetector;
@ -48,6 +44,10 @@ import org.springframework.util.ConcurrentReferenceHashMap;
* Abstract base class for {@link HttpMessageConverter} implementations that * Abstract base class for {@link HttpMessageConverter} implementations that
* use Kotlin serialization. * use Kotlin serialization.
* *
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
*
* @author Andreas Ahlenstorf * @author Andreas Ahlenstorf
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Juergen Hoeller * @author Juergen Hoeller
@ -160,9 +160,6 @@ public abstract class AbstractKotlinSerializationHttpMessageConverter<T extends
catch (IllegalArgumentException ignored) { catch (IllegalArgumentException ignored) {
} }
if (serializer != null) { if (serializer != null) {
if (hasPolymorphism(serializer.getDescriptor(), new HashSet<>())) {
return null;
}
this.kTypeSerializerCache.put(type, serializer); this.kTypeSerializerCache.put(type, serializer);
} }
} }
@ -179,29 +176,12 @@ public abstract class AbstractKotlinSerializationHttpMessageConverter<T extends
catch (IllegalArgumentException ignored) { catch (IllegalArgumentException ignored) {
} }
if (serializer != null) { if (serializer != null) {
if (hasPolymorphism(serializer.getDescriptor(), new HashSet<>())) {
return null;
}
this.typeSerializerCache.put(type, serializer); this.typeSerializerCache.put(type, serializer);
} }
} }
return serializer; return serializer;
} }
private boolean hasPolymorphism(SerialDescriptor descriptor, Set<String> alreadyProcessed) {
alreadyProcessed.add(descriptor.getSerialName());
if (descriptor.getKind().equals(PolymorphicKind.OPEN.INSTANCE)) {
return true;
}
for (int i = 0 ; i < descriptor.getElementsCount() ; i++) {
SerialDescriptor elementDescriptor = descriptor.getElementDescriptor(i);
if (!alreadyProcessed.contains(elementDescriptor.getSerialName()) && hasPolymorphism(elementDescriptor, alreadyProcessed)) {
return true;
}
}
return false;
}
@Override @Override
protected boolean supportsRepeatableWrites(Object object) { protected boolean supportsRepeatableWrites(Object object) {
return true; return true;

6
spring-web/src/main/java/org/springframework/http/converter/KotlinSerializationBinaryHttpMessageConverter.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -31,6 +31,10 @@ import org.springframework.util.StreamUtils;
* Abstract base class for {@link HttpMessageConverter} implementations that * Abstract base class for {@link HttpMessageConverter} implementations that
* defer to Kotlin {@linkplain BinaryFormat binary serializers}. * defer to Kotlin {@linkplain BinaryFormat binary serializers}.
* *
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
*
* @author Andreas Ahlenstorf * @author Andreas Ahlenstorf
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Juergen Hoeller * @author Juergen Hoeller

6
spring-web/src/main/java/org/springframework/http/converter/KotlinSerializationStringHttpMessageConverter.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -34,6 +34,10 @@ import org.springframework.util.StreamUtils;
* Abstract base class for {@link HttpMessageConverter} implementations that * Abstract base class for {@link HttpMessageConverter} implementations that
* defer to Kotlin {@linkplain StringFormat string serializers}. * defer to Kotlin {@linkplain StringFormat string serializers}.
* *
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
*
* @author Andreas Ahlenstorf * @author Andreas Ahlenstorf
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Juergen Hoeller * @author Juergen Hoeller

10
spring-web/src/main/java/org/springframework/http/converter/cbor/KotlinSerializationCborHttpMessageConverter.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -25,12 +25,12 @@ import org.springframework.http.converter.KotlinSerializationBinaryHttpMessageCo
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter} * Implementation of {@link org.springframework.http.converter.HttpMessageConverter}
* that can read and write CBOR using * that can read and write CBOR using
* <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>. * <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>.
*
* <p>This converter can be used to bind {@code @Serializable} Kotlin classes,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphic serialization</a>
* is not supported.
* It supports {@code application/cbor}. * It supports {@code application/cbor}.
* *
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
*
* @author Iain Henderson * @author Iain Henderson
* @since 6.0 * @since 6.0
*/ */

10
spring-web/src/main/java/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverter.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -25,13 +25,13 @@ import org.springframework.http.converter.KotlinSerializationStringHttpMessageCo
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter} * Implementation of {@link org.springframework.http.converter.HttpMessageConverter}
* that can read and write JSON using * that can read and write JSON using
* <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>. * <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>.
*
* <p>This converter can be used to bind {@code @Serializable} Kotlin classes,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphic serialization</a>
* is not supported.
* It supports {@code application/json} and {@code application/*+json} with * It supports {@code application/json} and {@code application/*+json} with
* various character sets, {@code UTF-8} being the default. * various character sets, {@code UTF-8} being the default.
* *
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
*
* @author Andreas Ahlenstorf * @author Andreas Ahlenstorf
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Juergen Hoeller * @author Juergen Hoeller

10
spring-web/src/main/java/org/springframework/http/converter/protobuf/KotlinSerializationProtobufHttpMessageConverter.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -25,12 +25,12 @@ import org.springframework.http.converter.KotlinSerializationBinaryHttpMessageCo
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter} * Implementation of {@link org.springframework.http.converter.HttpMessageConverter}
* that can read and write Protocol Buffers using * that can read and write Protocol Buffers using
* <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>. * <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>.
*
* <p>This converter can be used to bind {@code @Serializable} Kotlin classes,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphic serialization</a>
* is not supported.
* It supports {@code application/x-protobuf}, {@code application/octet-stream}, and {@code application/vnd.google.protobuf}. * It supports {@code application/x-protobuf}, {@code application/octet-stream}, and {@code application/vnd.google.protobuf}.
* *
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
*
* @author Iain Henderson * @author Iain Henderson
* @since 6.0 * @since 6.0
*/ */

21
spring-web/src/test/kotlin/org/springframework/http/codec/cbor/KotlinSerializationCborDecoderTests.kt

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -42,7 +42,6 @@ import org.springframework.http.MediaType
@ExperimentalSerializationApi @ExperimentalSerializationApi
class KotlinSerializationCborDecoderTests : AbstractDecoderTests<KotlinSerializationCborDecoder>(KotlinSerializationCborDecoder()) { class KotlinSerializationCborDecoderTests : AbstractDecoderTests<KotlinSerializationCborDecoder>(KotlinSerializationCborDecoder()) {
@Suppress("UsePropertyAccessSyntax", "DEPRECATION")
@Test @Test
override fun canDecode() { override fun canDecode() {
assertThat(decoder.canDecode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_CBOR)).isTrue() assertThat(decoder.canDecode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_CBOR)).isTrue()
@ -51,11 +50,13 @@ class KotlinSerializationCborDecoderTests : AbstractDecoderTests<KotlinSerializa
assertThat(decoder.canDecode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_XML)).isFalse() assertThat(decoder.canDecode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_XML)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isTrue() assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), MediaType.APPLICATION_CBOR)).isFalse() assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, OrderedImpl::class.java), MediaType.APPLICATION_CBOR)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), MediaType.APPLICATION_CBOR)).isTrue() assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isTrue() assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_PDF)).isFalse() assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_PDF)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClass(Ordered::class.java), MediaType.APPLICATION_CBOR)).isFalse() assertThat(decoder.canDecode(ResolvableType.forClass(Ordered::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClass(OrderedImpl::class.java), MediaType.APPLICATION_CBOR)).isFalse()
} }
@Test @Test
@ -92,6 +93,14 @@ class KotlinSerializationCborDecoderTests : AbstractDecoderTests<KotlinSerializa
} }
} }
@Serializable
data class Pojo(val foo: String, val bar: String, val pojo: Pojo? = null)
class OrderedImpl : Ordered {
override fun getOrder(): Int {
return 0
}
}
} }
@Serializable
data class Pojo(val foo: String, val bar: String, val pojo: Pojo? = null)

14
spring-web/src/test/kotlin/org/springframework/http/codec/cbor/KotlinSerializationCborEncoderTests.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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -50,10 +50,9 @@ class KotlinSerializationCborEncoderTests : AbstractEncoderTests<KotlinSerializa
assertThat(encoder.canEncode(pojoType, null)).isTrue() assertThat(encoder.canEncode(pojoType, null)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isTrue() assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), MediaType.APPLICATION_CBOR)).isFalse() assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), MediaType.APPLICATION_CBOR)).isTrue() assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isTrue() assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_PDF)).isFalse()
} }
@Test @Test
@ -93,11 +92,18 @@ class KotlinSerializationCborEncoderTests : AbstractEncoderTests<KotlinSerializa
assertThat(encoder.canEncode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_XML)).isFalse() assertThat(encoder.canEncode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_XML)).isFalse()
val sseType = ResolvableType.forClass(ServerSentEvent::class.java) val sseType = ResolvableType.forClass(ServerSentEvent::class.java)
assertThat(encoder.canEncode(sseType, MediaType.APPLICATION_CBOR)).isFalse() assertThat(encoder.canEncode(sseType, MediaType.APPLICATION_CBOR)).isFalse()
assertThat(encoder.canEncode(ResolvableType.forClass(Ordered::class.java), MediaType.APPLICATION_CBOR)).isFalse() assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, OrderedImpl::class.java), MediaType.APPLICATION_CBOR)).isFalse()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_PDF)).isFalse()
} }
@Serializable @Serializable
data class Pojo(val foo: String, val bar: String, val pojo: Pojo? = null) data class Pojo(val foo: String, val bar: String, val pojo: Pojo? = null)
class OrderedImpl : Ordered {
override fun getOrder(): Int {
return 0
}
}
} }

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,6 +17,8 @@
package org.springframework.http.codec.json package org.springframework.http.codec.json
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.springframework.core.MethodParameter import org.springframework.core.MethodParameter
@ -24,11 +26,11 @@ import org.springframework.core.Ordered
import org.springframework.core.ResolvableType import org.springframework.core.ResolvableType
import org.springframework.core.codec.DecodingException import org.springframework.core.codec.DecodingException
import org.springframework.core.io.buffer.DataBuffer import org.springframework.core.io.buffer.DataBuffer
import org.springframework.core.io.buffer.DataBufferUtils
import org.springframework.core.testfixture.codec.AbstractDecoderTests import org.springframework.core.testfixture.codec.AbstractDecoderTests
import org.springframework.http.MediaType import org.springframework.http.MediaType
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
import reactor.test.StepVerifier
import reactor.test.StepVerifier.FirstStep import reactor.test.StepVerifier.FirstStep
import java.math.BigDecimal import java.math.BigDecimal
import java.nio.charset.Charset import java.nio.charset.Charset
@ -58,11 +60,13 @@ class KotlinSerializationJsonDecoderTests : AbstractDecoderTests<KotlinSerializa
MediaType("application", "json", StandardCharsets.ISO_8859_1))).isTrue() MediaType("application", "json", StandardCharsets.ISO_8859_1))).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), MediaType.APPLICATION_JSON)).isTrue() assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), MediaType.APPLICATION_JSON)).isFalse() assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, OrderedImpl::class.java), MediaType.APPLICATION_JSON)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), MediaType.APPLICATION_JSON)).isTrue() assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_JSON)).isTrue() assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_PDF)).isFalse() assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_PDF)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClass(Ordered::class.java), MediaType.APPLICATION_JSON)).isFalse() assertThat(decoder.canDecode(ResolvableType.forClass(Ordered::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClass(OrderedImpl::class.java), MediaType.APPLICATION_JSON)).isFalse()
assertThat(decoder.canDecode(ResolvableType.NONE, MediaType.APPLICATION_JSON)).isFalse() assertThat(decoder.canDecode(ResolvableType.NONE, MediaType.APPLICATION_JSON)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClass(BigDecimal::class.java), MediaType.APPLICATION_JSON)).isFalse() assertThat(decoder.canDecode(ResolvableType.forClass(BigDecimal::class.java), MediaType.APPLICATION_JSON)).isFalse()
@ -84,6 +88,31 @@ class KotlinSerializationJsonDecoderTests : AbstractDecoderTests<KotlinSerializa
}, null, null) }, null, null)
} }
@Test
@Suppress("UNCHECKED_CAST")
fun polymorphicDecode() {
val json = Json {
serializersModule = SerializersModule {
polymorphic(ISimpleSerializableBean::class, SimpleSerializableBean::class, SimpleSerializableBean.serializer())
}
}
val customDecoder = KotlinSerializationJsonDecoder(json)
val input = Flux.concat(
stringBuffer("{\"type\":\"org.springframework.http.codec.json.KotlinSerializationJsonDecoderTests.SimpleSerializableBean\",\"name\":\"foo\"}\n"),
stringBuffer("{\"type\":\"org.springframework.http.codec.json.KotlinSerializationJsonDecoderTests.SimpleSerializableBean\",\"name\":\"bar\"}\n"),
stringBuffer("{\"type\":\"org.springframework.http.codec.json.KotlinSerializationJsonDecoderTests.SimpleSerializableBean\",\"name\":\"baz\"}\n")
)
val resolvableType = ResolvableType.forClass(ISimpleSerializableBean::class.java)
val result: Flux<ISimpleSerializableBean> = customDecoder.decode(input, resolvableType, null, null) as Flux<ISimpleSerializableBean>
val step: FirstStep<ISimpleSerializableBean> = StepVerifier.create(result)
step.expectNext(SimpleSerializableBean("foo"))
.expectNext(SimpleSerializableBean("bar"))
.expectNext(SimpleSerializableBean("baz"))
.verifyComplete()
}
@Test @Test
fun decodeWithUnexpectedFormat() { fun decodeWithUnexpectedFormat() {
val input = Flux.concat( val input = Flux.concat(
@ -188,4 +217,17 @@ class KotlinSerializationJsonDecoderTests : AbstractDecoderTests<KotlinSerializa
fun handleMapWithNullable(map: Map<String, String?>) = map fun handleMapWithNullable(map: Map<String, String?>) = map
interface ISimpleSerializableBean {
val name: String
}
@Serializable
data class SimpleSerializableBean(override val name: String): ISimpleSerializableBean
class OrderedImpl : Ordered {
override fun getOrder(): Int {
return 0
}
}
} }

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,11 +17,14 @@
package org.springframework.http.codec.json package org.springframework.http.codec.json
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.springframework.core.MethodParameter import org.springframework.core.MethodParameter
import org.springframework.core.Ordered import org.springframework.core.Ordered
import org.springframework.core.ResolvableType import org.springframework.core.ResolvableType
import org.springframework.core.io.buffer.DataBuffer
import org.springframework.core.testfixture.codec.AbstractEncoderTests import org.springframework.core.testfixture.codec.AbstractEncoderTests
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.http.codec.ServerSentEvent import org.springframework.http.codec.ServerSentEvent
@ -53,11 +56,9 @@ class KotlinSerializationJsonEncoderTests : AbstractEncoderTests<KotlinSerializa
MediaType("application", "json", StandardCharsets.US_ASCII))).isTrue() MediaType("application", "json", StandardCharsets.US_ASCII))).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), MediaType.APPLICATION_JSON)).isTrue() assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), MediaType.APPLICATION_JSON)).isFalse() assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), MediaType.APPLICATION_JSON)).isTrue() assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_JSON)).isTrue() assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_PDF)).isFalse()
assertThat(encoder.canEncode(ResolvableType.NONE, MediaType.APPLICATION_JSON)).isFalse()
assertThat(encoder.canEncode(pojoType, MediaType.APPLICATION_NDJSON)).isTrue() assertThat(encoder.canEncode(pojoType, MediaType.APPLICATION_NDJSON)).isTrue()
} }
@ -116,6 +117,28 @@ class KotlinSerializationJsonEncoderTests : AbstractEncoderTests<KotlinSerializa
} }
} }
@Test
fun encodePolymorphicStream() {
val json = Json {
serializersModule = SerializersModule {
polymorphic(ISimpleSerializableBean::class, SimpleSerializableBean::class, SimpleSerializableBean.serializer())
}
}
val customEncoder = KotlinSerializationJsonEncoder(json)
val input = Flux<ISimpleSerializableBean>.just(
SimpleSerializableBean("foo"),
SimpleSerializableBean("bar"),
SimpleSerializableBean("baz")
)
val result = customEncoder.encode(input, this.bufferFactory, ResolvableType.forClass(ISimpleSerializableBean::class.java), MediaType.APPLICATION_NDJSON, null)
val step = StepVerifier.create<DataBuffer>(result)
step
.consumeNextWith { expectString("{\"type\":\"org.springframework.http.codec.json.KotlinSerializationJsonEncoderTests.SimpleSerializableBean\",\"name\":\"foo\"}\n").accept(it) }
.consumeNextWith { expectString("{\"type\":\"org.springframework.http.codec.json.KotlinSerializationJsonEncoderTests.SimpleSerializableBean\",\"name\":\"bar\"}\n").accept(it) }
.consumeNextWith { expectString("{\"type\":\"org.springframework.http.codec.json.KotlinSerializationJsonEncoderTests.SimpleSerializableBean\",\"name\":\"baz\"}\n").accept(it) }
.verifyComplete()
}
@Test @Test
fun encodeMono() { fun encodeMono() {
val input = Mono.just(Pojo("foo", "bar")) val input = Mono.just(Pojo("foo", "bar"))
@ -141,8 +164,10 @@ class KotlinSerializationJsonEncoderTests : AbstractEncoderTests<KotlinSerializa
assertThat(encoder.canEncode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_XML)).isFalse() assertThat(encoder.canEncode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_XML)).isFalse()
val sseType = ResolvableType.forClass(ServerSentEvent::class.java) val sseType = ResolvableType.forClass(ServerSentEvent::class.java)
assertThat(encoder.canEncode(sseType, MediaType.APPLICATION_JSON)).isFalse() assertThat(encoder.canEncode(sseType, MediaType.APPLICATION_JSON)).isFalse()
assertThat(encoder.canEncode(ResolvableType.forClass(Ordered::class.java), MediaType.APPLICATION_JSON)).isFalse()
assertThat(encoder.canEncode(ResolvableType.forClass(BigDecimal::class.java), null)).isFalse() assertThat(encoder.canEncode(ResolvableType.forClass(BigDecimal::class.java), null)).isFalse()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, OrderedImpl::class.java), MediaType.APPLICATION_JSON)).isFalse()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_PDF)).isFalse()
assertThat(encoder.canEncode(ResolvableType.NONE, MediaType.APPLICATION_JSON)).isFalse()
} }
@Test @Test
@ -165,4 +190,17 @@ class KotlinSerializationJsonEncoderTests : AbstractEncoderTests<KotlinSerializa
val value: Int val value: Int
get() = 42 get() = 42
interface ISimpleSerializableBean {
val name: String
}
@Serializable
data class SimpleSerializableBean(override val name: String): ISimpleSerializableBean
class OrderedImpl : Ordered {
override fun getOrder(): Int {
return 0
}
}
} }

20
spring-web/src/test/kotlin/org/springframework/http/codec/protobuf/KotlinSerializationProtobufDecoderTests.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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -48,10 +48,12 @@ class KotlinSerializationProtobufDecoderTests : AbstractDecoderTests<KotlinSeria
assertThat(decoder.canDecode(ResolvableType.forClass(Pojo::class.java),mimeType)).isTrue() assertThat(decoder.canDecode(ResolvableType.forClass(Pojo::class.java),mimeType)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), mimeType)).isTrue() assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), mimeType)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), mimeType)).isFalse() assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), mimeType)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, OrderedImpl::class.java), mimeType)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), mimeType)).isTrue() assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), mimeType)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), mimeType)).isTrue() assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), mimeType)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClass(Ordered::class.java), mimeType)).isFalse() assertThat(decoder.canDecode(ResolvableType.forClass(Ordered::class.java), mimeType)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClass(OrderedImpl::class.java), mimeType)).isFalse()
} }
assertThat(decoder.canDecode(ResolvableType.forClass(Pojo::class.java), null)).isTrue() assertThat(decoder.canDecode(ResolvableType.forClass(Pojo::class.java), null)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClass(String::class.java), null)).isFalse() assertThat(decoder.canDecode(ResolvableType.forClass(String::class.java), null)).isFalse()
@ -111,6 +113,14 @@ class KotlinSerializationProtobufDecoderTests : AbstractDecoderTests<KotlinSeria
} }
} }
@Serializable
data class Pojo(val foo: String, val bar: String, val pojo: Pojo? = null)
class OrderedImpl : Ordered {
override fun getOrder(): Int {
return 0
}
}
} }
@Serializable
data class Pojo(val foo: String, val bar: String, val pojo: Pojo? = null)

13
spring-web/src/test/kotlin/org/springframework/http/codec/protobuf/KotlinSerializationProtobufEncoderTests.kt

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -39,7 +39,6 @@ import reactor.test.StepVerifier.FirstStep
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Iain Henderson * @author Iain Henderson
*/ */
@Suppress("UsePropertyAccessSyntax")
@ExperimentalSerializationApi @ExperimentalSerializationApi
class KotlinSerializationProtobufEncoderTests : AbstractEncoderTests<KotlinSerializationProtobufEncoder>(KotlinSerializationProtobufEncoder()) { class KotlinSerializationProtobufEncoderTests : AbstractEncoderTests<KotlinSerializationProtobufEncoder>(KotlinSerializationProtobufEncoder()) {
@ -59,7 +58,7 @@ class KotlinSerializationProtobufEncoderTests : AbstractEncoderTests<KotlinSeria
assertThat(encoder.canEncode(pojoType, mediaType)).isTrue() assertThat(encoder.canEncode(pojoType, mediaType)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), mimeType)).isTrue() assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), mimeType)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), mimeType)).isFalse() assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), mimeType)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), mimeType)).isTrue() assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), mimeType)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), mimeType)).isTrue() assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), mimeType)).isTrue()
} }
@ -104,7 +103,7 @@ class KotlinSerializationProtobufEncoderTests : AbstractEncoderTests<KotlinSeria
val sseType = ResolvableType.forClass(ServerSentEvent::class.java) val sseType = ResolvableType.forClass(ServerSentEvent::class.java)
for (mediaType in mediaTypes) { for (mediaType in mediaTypes) {
assertThat(encoder.canEncode(sseType, mediaType)).isFalse() assertThat(encoder.canEncode(sseType, mediaType)).isFalse()
assertThat(encoder.canEncode(ResolvableType.forClass(Ordered::class.java), mediaType)).isFalse() assertThat(encoder.canEncode(ResolvableType.forClass(OrderedImpl::class.java), mediaType)).isFalse()
} }
} }
@ -112,4 +111,10 @@ class KotlinSerializationProtobufEncoderTests : AbstractEncoderTests<KotlinSeria
@Serializable @Serializable
data class Pojo(val foo: String, val bar: String, val pojo: Pojo? = null) data class Pojo(val foo: String, val bar: String, val pojo: Pojo? = null)
class OrderedImpl : Ordered {
override fun getOrder(): Int {
return 0
}
}
} }

28
spring-web/src/test/kotlin/org/springframework/http/converter/cbor/KotlinSerializationCborHttpMessageConverterTests.kt

@ -43,7 +43,6 @@ import org.springframework.web.testfixture.http.MockHttpOutputMessage
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Iain Henderson * @author Iain Henderson
*/ */
@Suppress("UsePropertyAccessSyntax")
@ExperimentalSerializationApi @ExperimentalSerializationApi
class KotlinSerializationCborHttpMessageConverterTests { class KotlinSerializationCborHttpMessageConverterTests {
@ -66,19 +65,21 @@ class KotlinSerializationCborHttpMessageConverterTests {
assertThat(converter.canRead(String::class.java, MediaType.APPLICATION_CBOR)).isTrue() assertThat(converter.canRead(String::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canRead(NotSerializableBean::class.java, MediaType.APPLICATION_CBOR)).isFalse() assertThat(converter.canRead(NotSerializableBean::class.java, MediaType.APPLICATION_CBOR)).isFalse()
assertThat(converter.canRead(Map::class.java, MediaType.APPLICATION_CBOR)).isFalse() assertThat(converter.canRead(Map::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<Map<String, SerializableBean>>(), MediaType.APPLICATION_CBOR)).isTrue() assertThat(converter.canRead(resolvableTypeOf<Map<String, SerializableBean>>(), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canRead(List::class.java, MediaType.APPLICATION_CBOR)).isFalse() assertThat(converter.canRead(List::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<List<SerializableBean>>(), MediaType.APPLICATION_CBOR)).isTrue() assertThat(converter.canRead(resolvableTypeOf<List<SerializableBean>>(), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canRead(Set::class.java, MediaType.APPLICATION_CBOR)).isFalse() assertThat(converter.canRead(Set::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<Set<SerializableBean>>(), MediaType.APPLICATION_CBOR)).isTrue() assertThat(converter.canRead(resolvableTypeOf<Set<SerializableBean>>(), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<List<Int>>(), MediaType.APPLICATION_CBOR)).isTrue() assertThat(converter.canRead(resolvableTypeOf<List<Int>>(), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<ArrayList<Int>>(), MediaType.APPLICATION_CBOR)).isTrue() assertThat(converter.canRead(resolvableTypeOf<ArrayList<Int>>(), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<List<Int>>(), MediaType.APPLICATION_JSON)).isFalse() assertThat(converter.canRead(resolvableTypeOf<List<Int>>(), MediaType.APPLICATION_JSON)).isFalse()
assertThat(converter.canRead(resolvableTypeOf<Ordered>(), MediaType.APPLICATION_CBOR)).isFalse() assertThat(converter.canRead(resolvableTypeOf<Ordered>(), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<List<Ordered>>(), MediaType.APPLICATION_CBOR)).isFalse() assertThat(converter.canRead(resolvableTypeOf<List<Ordered>>(), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<OrderedImpl>(), MediaType.APPLICATION_CBOR)).isFalse()
assertThat(converter.canRead(resolvableTypeOf<List<OrderedImpl>>(), MediaType.APPLICATION_CBOR)).isFalse()
} }
@Test @Test
@ -88,18 +89,19 @@ class KotlinSerializationCborHttpMessageConverterTests {
assertThat(converter.canWrite(String::class.java, MediaType.APPLICATION_CBOR)).isTrue() assertThat(converter.canWrite(String::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canWrite(NotSerializableBean::class.java, MediaType.APPLICATION_CBOR)).isFalse() assertThat(converter.canWrite(NotSerializableBean::class.java, MediaType.APPLICATION_CBOR)).isFalse()
assertThat(converter.canWrite(Map::class.java, MediaType.APPLICATION_CBOR)).isFalse() assertThat(converter.canWrite(Map::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canWrite(resolvableTypeOf<Map<String, SerializableBean>>(), Map::class.java, MediaType.APPLICATION_CBOR)).isTrue() assertThat(converter.canWrite(resolvableTypeOf<Map<String, SerializableBean>>(), Map::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canWrite(List::class.java, MediaType.APPLICATION_CBOR)).isFalse() assertThat(converter.canWrite(List::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canWrite(resolvableTypeOf<List<SerializableBean>>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue() assertThat(converter.canWrite(resolvableTypeOf<List<SerializableBean>>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canWrite(Set::class.java, MediaType.APPLICATION_CBOR)).isFalse() assertThat(converter.canWrite(Set::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canWrite(resolvableTypeOf<Set<SerializableBean>>(), Set::class.java, MediaType.APPLICATION_CBOR)).isTrue() assertThat(converter.canWrite(resolvableTypeOf<Set<SerializableBean>>(), Set::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canWrite(resolvableTypeOf<List<Int>>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue() assertThat(converter.canWrite(resolvableTypeOf<List<Int>>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canWrite(resolvableTypeOf<ArrayList<Int>>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue() assertThat(converter.canWrite(resolvableTypeOf<ArrayList<Int>>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canWrite(resolvableTypeOf<List<Int>>(), List::class.java, MediaType.APPLICATION_JSON)).isFalse() assertThat(converter.canWrite(resolvableTypeOf<List<Int>>(), List::class.java, MediaType.APPLICATION_JSON)).isFalse()
assertThat(converter.canWrite(resolvableTypeOf<Ordered>(), Ordered::class.java, MediaType.APPLICATION_CBOR)).isFalse() assertThat(converter.canWrite(resolvableTypeOf<Ordered>(), Ordered::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canWrite(resolvableTypeOf<OrderedImpl>(), Ordered::class.java, MediaType.APPLICATION_CBOR)).isFalse()
} }
@Test @Test
@ -228,4 +230,10 @@ class KotlinSerializationCborHttpMessageConverterTests {
return ResolvableType.forType((superType as ParameterizedType).actualTypeArguments.first()!!) return ResolvableType.forType((superType as ParameterizedType).actualTypeArguments.first()!!)
} }
class OrderedImpl : Ordered {
override fun getOrder(): Int {
return 0
}
}
} }

77
spring-web/src/test/kotlin/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverterTests.kt

@ -17,6 +17,8 @@
package org.springframework.http.converter.json package org.springframework.http.converter.json
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@ -53,19 +55,21 @@ class KotlinSerializationJsonHttpMessageConverterTests {
assertThat(converter.canRead(String::class.java, MediaType.APPLICATION_JSON)).isTrue() assertThat(converter.canRead(String::class.java, MediaType.APPLICATION_JSON)).isTrue()
assertThat(converter.canRead(NotSerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse() assertThat(converter.canRead(NotSerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse()
assertThat(converter.canRead(Map::class.java, MediaType.APPLICATION_JSON)).isFalse() assertThat(converter.canRead(Map::class.java, MediaType.APPLICATION_JSON)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<Map<String, SerializableBean>>(), MediaType.APPLICATION_JSON)).isTrue() assertThat(converter.canRead(resolvableTypeOf<Map<String, SerializableBean>>(), MediaType.APPLICATION_JSON)).isTrue()
assertThat(converter.canRead(List::class.java, MediaType.APPLICATION_JSON)).isFalse() assertThat(converter.canRead(List::class.java, MediaType.APPLICATION_JSON)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<List<SerializableBean>>(), MediaType.APPLICATION_JSON)).isTrue() assertThat(converter.canRead(resolvableTypeOf<List<SerializableBean>>(), MediaType.APPLICATION_JSON)).isTrue()
assertThat(converter.canRead(Set::class.java, MediaType.APPLICATION_JSON)).isFalse() assertThat(converter.canRead(Set::class.java, MediaType.APPLICATION_JSON)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<Set<SerializableBean>>(), MediaType.APPLICATION_JSON)).isTrue() assertThat(converter.canRead(resolvableTypeOf<Set<SerializableBean>>(), MediaType.APPLICATION_JSON)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<List<Int>>(), MediaType.APPLICATION_JSON)).isTrue() assertThat(converter.canRead(resolvableTypeOf<List<Int>>(), MediaType.APPLICATION_JSON)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<ArrayList<Int>>(), MediaType.APPLICATION_JSON)).isTrue() assertThat(converter.canRead(resolvableTypeOf<ArrayList<Int>>(), MediaType.APPLICATION_JSON)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<List<Int>>(), MediaType.APPLICATION_PDF)).isFalse() assertThat(converter.canRead(resolvableTypeOf<List<Int>>(), MediaType.APPLICATION_PDF)).isFalse()
assertThat(converter.canRead(resolvableTypeOf<Ordered>(), MediaType.APPLICATION_JSON)).isFalse() assertThat(converter.canRead(resolvableTypeOf<Ordered>(), MediaType.APPLICATION_JSON)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<List<Ordered>>(), MediaType.APPLICATION_JSON)).isFalse() assertThat(converter.canRead(resolvableTypeOf<List<Ordered>>(), MediaType.APPLICATION_JSON)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<OrderedImpl>(), MediaType.APPLICATION_JSON)).isFalse()
assertThat(converter.canRead(resolvableTypeOf<List<OrderedImpl>>(), MediaType.APPLICATION_JSON)).isFalse()
assertThat(converter.canRead(ResolvableType.forType(ResolvableType.NONE.type), MediaType.APPLICATION_JSON)).isFalse() assertThat(converter.canRead(ResolvableType.forType(ResolvableType.NONE.type), MediaType.APPLICATION_JSON)).isFalse()
@ -79,18 +83,19 @@ class KotlinSerializationJsonHttpMessageConverterTests {
assertThat(converter.canWrite(String::class.java, MediaType.APPLICATION_JSON)).isTrue() assertThat(converter.canWrite(String::class.java, MediaType.APPLICATION_JSON)).isTrue()
assertThat(converter.canWrite(NotSerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse() assertThat(converter.canWrite(NotSerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse()
assertThat(converter.canWrite(Map::class.java, MediaType.APPLICATION_JSON)).isFalse() assertThat(converter.canWrite(Map::class.java, MediaType.APPLICATION_JSON)).isTrue()
assertThat(converter.canWrite(resolvableTypeOf<Map<String, SerializableBean>>(), Map::class.java, MediaType.APPLICATION_JSON)).isTrue() assertThat(converter.canWrite(resolvableTypeOf<Map<String, SerializableBean>>(), Map::class.java, MediaType.APPLICATION_JSON)).isTrue()
assertThat(converter.canWrite(List::class.java, MediaType.APPLICATION_JSON)).isFalse() assertThat(converter.canWrite(List::class.java, MediaType.APPLICATION_JSON)).isTrue()
assertThat(converter.canWrite(resolvableTypeOf<List<SerializableBean>>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue() assertThat(converter.canWrite(resolvableTypeOf<List<SerializableBean>>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue()
assertThat(converter.canWrite(Set::class.java, MediaType.APPLICATION_JSON)).isFalse() assertThat(converter.canWrite(Set::class.java, MediaType.APPLICATION_JSON)).isTrue()
assertThat(converter.canWrite(resolvableTypeOf<Set<SerializableBean>>(), Set::class.java, MediaType.APPLICATION_JSON)).isTrue() assertThat(converter.canWrite(resolvableTypeOf<Set<SerializableBean>>(), Set::class.java, MediaType.APPLICATION_JSON)).isTrue()
assertThat(converter.canWrite(resolvableTypeOf<List<Int>>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue() assertThat(converter.canWrite(resolvableTypeOf<List<Int>>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue()
assertThat(converter.canWrite(resolvableTypeOf<ArrayList<Int>>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue() assertThat(converter.canWrite(resolvableTypeOf<ArrayList<Int>>(), List::class.java, MediaType.APPLICATION_JSON)).isTrue()
assertThat(converter.canWrite(resolvableTypeOf<List<Int>>(), List::class.java, MediaType.APPLICATION_PDF)).isFalse() assertThat(converter.canWrite(resolvableTypeOf<List<Int>>(), List::class.java, MediaType.APPLICATION_PDF)).isFalse()
assertThat(converter.canWrite(resolvableTypeOf<Ordered>(), Ordered::class.java, MediaType.APPLICATION_JSON)).isFalse() assertThat(converter.canWrite(resolvableTypeOf<Ordered>(), Ordered::class.java, MediaType.APPLICATION_JSON)).isTrue()
assertThat(converter.canWrite(resolvableTypeOf<OrderedImpl>(), OrderedImpl::class.java, MediaType.APPLICATION_JSON)).isFalse()
assertThat(converter.canWrite(ResolvableType.NONE, SerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse() assertThat(converter.canWrite(ResolvableType.NONE, SerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse()
@ -246,6 +251,26 @@ class KotlinSerializationJsonHttpMessageConverterTests {
assertThat(result).containsExactlyEntriesOf(mapOf("value" to null)) assertThat(result).containsExactlyEntriesOf(mapOf("value" to null))
} }
@Test
@Suppress("UNCHECKED_CAST")
fun readWithPolymorphism() {
val json = Json {
serializersModule = SerializersModule {
polymorphic(ISimpleSerializableBean::class, SimpleSerializableBean::class, SimpleSerializableBean.serializer())
}
}
val customConverter = KotlinSerializationJsonHttpMessageConverter(json)
val body = """[{"type":"org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverterTests.SimpleSerializableBean","name":"foo"},""" +
"""{"type":"org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverterTests.SimpleSerializableBean","name":"bar"}]"""
val inputMessage = MockHttpInputMessage(body.toByteArray(StandardCharsets.UTF_8))
inputMessage.headers.contentType = MediaType.APPLICATION_JSON
val resolvableType = ResolvableType.forClassWithGenerics(List::class.java, ISimpleSerializableBean::class.java)
val result = customConverter.read(resolvableType, inputMessage, null) as List<ISimpleSerializableBean>
assertThat(result).containsExactly(SimpleSerializableBean("foo"), SimpleSerializableBean("bar"))
}
@Test @Test
fun writeObject() { fun writeObject() {
val outputMessage = MockHttpOutputMessage() val outputMessage = MockHttpOutputMessage()
@ -398,6 +423,27 @@ class KotlinSerializationJsonHttpMessageConverterTests {
assertThat(result).isEqualTo("42") assertThat(result).isEqualTo("42")
} }
@Test
fun writeWithPolymorphism() {
val json = Json {
serializersModule = SerializersModule {
polymorphic(ISimpleSerializableBean::class, SimpleSerializableBean::class, SimpleSerializableBean.serializer())
}
}
val customConverter = KotlinSerializationJsonHttpMessageConverter(json)
val outputMessage = MockHttpOutputMessage()
val value = listOf(SimpleSerializableBean("foo"), SimpleSerializableBean("bar"))
customConverter.write(value, ResolvableType.forClassWithGenerics(List::class.java, ISimpleSerializableBean::class.java), null, outputMessage, null)
val result = outputMessage.getBodyAsString(StandardCharsets.UTF_8)
assertThat(outputMessage.headers.containsHeaderValue("Content-Type", "application/json")).isTrue()
assertThat(result).isEqualTo(
"""[{"type":"org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverterTests.SimpleSerializableBean","name":"foo"},""" +
"""{"type":"org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverterTests.SimpleSerializableBean","name":"bar"}]""")
}
@Serializable @Serializable
@Suppress("ArrayInDataClass") @Suppress("ArrayInDataClass")
@ -426,4 +472,17 @@ class KotlinSerializationJsonHttpMessageConverterTests {
val value: Int val value: Int
get() = 42 get() = 42
interface ISimpleSerializableBean {
val name: String
}
@Serializable
data class SimpleSerializableBean(override val name: String): ISimpleSerializableBean
class OrderedImpl : Ordered {
override fun getOrder(): Int {
return 0
}
}
} }

28
spring-web/src/test/kotlin/org/springframework/http/converter/protobuf/KotlinSerializationProtobufHttpMessageConverterTests.kt

@ -41,7 +41,6 @@ import kotlin.reflect.typeOf
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Iain Henderson * @author Iain Henderson
*/ */
@Suppress("UsePropertyAccessSyntax")
@ExperimentalSerializationApi @ExperimentalSerializationApi
class KotlinSerializationProtobufHttpMessageConverterTests { class KotlinSerializationProtobufHttpMessageConverterTests {
@ -68,18 +67,20 @@ class KotlinSerializationProtobufHttpMessageConverterTests {
assertThat(converter.canRead(String::class.java, mimeType)).isTrue() assertThat(converter.canRead(String::class.java, mimeType)).isTrue()
assertThat(converter.canRead(NotSerializableBean::class.java, mimeType)).isFalse() assertThat(converter.canRead(NotSerializableBean::class.java, mimeType)).isFalse()
assertThat(converter.canRead(Map::class.java, mimeType)).isFalse() assertThat(converter.canRead(Map::class.java, mimeType)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<Map<String, SerializableBean>>(), mimeType)).isTrue() assertThat(converter.canRead(resolvableTypeOf<Map<String, SerializableBean>>(), mimeType)).isTrue()
assertThat(converter.canRead(List::class.java, mimeType)).isFalse() assertThat(converter.canRead(List::class.java, mimeType)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<List<SerializableBean>>(), mimeType)).isTrue() assertThat(converter.canRead(resolvableTypeOf<List<SerializableBean>>(), mimeType)).isTrue()
assertThat(converter.canRead(Set::class.java, mimeType)).isFalse() assertThat(converter.canRead(Set::class.java, mimeType)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<Set<SerializableBean>>(), mimeType)).isTrue() assertThat(converter.canRead(resolvableTypeOf<Set<SerializableBean>>(), mimeType)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<List<Int>>(), mimeType)).isTrue() assertThat(converter.canRead(resolvableTypeOf<List<Int>>(), mimeType)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<ArrayList<Int>>(),mimeType)).isTrue() assertThat(converter.canRead(resolvableTypeOf<ArrayList<Int>>(),mimeType)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<Ordered>(), mimeType)).isFalse() assertThat(converter.canRead(resolvableTypeOf<Ordered>(), mimeType)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<List<Ordered>>(), mimeType)).isFalse() assertThat(converter.canRead(resolvableTypeOf<List<Ordered>>(), mimeType)).isTrue()
assertThat(converter.canRead(resolvableTypeOf<OrderedImpl>(), mimeType)).isFalse()
assertThat(converter.canRead(resolvableTypeOf<List<OrderedImpl>>(), mimeType)).isFalse()
} }
assertThat(converter.canRead(SerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse() assertThat(converter.canRead(SerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse()
assertThat(converter.canRead(resolvableTypeOf<List<Int>>(), MediaType.APPLICATION_JSON)).isFalse() assertThat(converter.canRead(resolvableTypeOf<List<Int>>(), MediaType.APPLICATION_JSON)).isFalse()
@ -92,17 +93,18 @@ class KotlinSerializationProtobufHttpMessageConverterTests {
assertThat(converter.canWrite(String::class.java, mimeType)).isTrue() assertThat(converter.canWrite(String::class.java, mimeType)).isTrue()
assertThat(converter.canWrite(NotSerializableBean::class.java, mimeType)).isFalse() assertThat(converter.canWrite(NotSerializableBean::class.java, mimeType)).isFalse()
assertThat(converter.canWrite(Map::class.java, mimeType)).isFalse() assertThat(converter.canWrite(Map::class.java, mimeType)).isTrue()
assertThat(converter.canWrite(resolvableTypeOf<Map<String, SerializableBean>>(), Map::class.java, mimeType)).isTrue() assertThat(converter.canWrite(resolvableTypeOf<Map<String, SerializableBean>>(), Map::class.java, mimeType)).isTrue()
assertThat(converter.canWrite(List::class.java, mimeType)).isFalse() assertThat(converter.canWrite(List::class.java, mimeType)).isTrue()
assertThat(converter.canWrite(resolvableTypeOf<List<SerializableBean>>(), List::class.java, mimeType)).isTrue() assertThat(converter.canWrite(resolvableTypeOf<List<SerializableBean>>(), List::class.java, mimeType)).isTrue()
assertThat(converter.canWrite(Set::class.java, mimeType)).isFalse() assertThat(converter.canWrite(Set::class.java, mimeType)).isTrue()
assertThat(converter.canWrite(resolvableTypeOf<Set<SerializableBean>>(), Set::class.java, mimeType)).isTrue() assertThat(converter.canWrite(resolvableTypeOf<Set<SerializableBean>>(), Set::class.java, mimeType)).isTrue()
assertThat(converter.canWrite(resolvableTypeOf<List<Int>>(), List::class.java, mimeType)).isTrue() assertThat(converter.canWrite(resolvableTypeOf<List<Int>>(), List::class.java, mimeType)).isTrue()
assertThat(converter.canWrite(resolvableTypeOf<ArrayList<Int>>(), List::class.java, mimeType)).isTrue() assertThat(converter.canWrite(resolvableTypeOf<ArrayList<Int>>(), List::class.java, mimeType)).isTrue()
assertThat(converter.canWrite(resolvableTypeOf<Ordered>(), Ordered::class.java, mimeType)).isFalse() assertThat(converter.canWrite(resolvableTypeOf<Ordered>(), Ordered::class.java, mimeType)).isTrue()
assertThat(converter.canWrite(resolvableTypeOf<Ordered>(), OrderedImpl::class.java, mimeType)).isTrue()
} }
assertThat(converter.canWrite(SerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse() assertThat(converter.canWrite(SerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse()
@ -243,4 +245,10 @@ class KotlinSerializationProtobufHttpMessageConverterTests {
return ResolvableType.forType((superType as ParameterizedType).actualTypeArguments.first()!!) return ResolvableType.forType((superType as ParameterizedType).actualTypeArguments.first()!!)
} }
class OrderedImpl : Ordered {
override fun getOrder(): Int {
return 0
}
}
} }

Loading…
Cancel
Save