Skip to content

Commit 19dd488

Browse files
committed
Prevent Kotlin Serialization codecs side effects
This commit updates Kotlin serialization codecs to perform an additional check invoking KotlinDetector#hasSerializableAnnotation to decide if the related type should be processed or not. The goal is to prevent in the default arrangement conflicts between general purpose codecs like Jackson and Kotlin serialization when both are used. New constructors allowing to specify a custom predicate are also introduced. See gh-35761
1 parent a68d607 commit 19dd488

19 files changed

+533
-85
lines changed

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

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.List;
2020
import java.util.Map;
21+
import java.util.function.Predicate;
2122

2223
import kotlinx.serialization.BinaryFormat;
2324
import kotlinx.serialization.KSerializer;
@@ -37,9 +38,11 @@
3738
* Abstract base class for {@link Decoder} implementations that defer to Kotlin
3839
* {@linkplain BinaryFormat binary serializers}.
3940
*
40-
* <p>As of Spring Framework 7.0,
41-
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
42-
* is supported.
41+
* <p>As of Spring Framework 7.0, by default it only decodes types annotated with
42+
* {@link kotlinx.serialization.Serializable @Serializable} at type or generics level
43+
* since it allows combined usage with other general purpose decoders without conflicts.
44+
* Alternative constructors with a {@code Predicate<ResolvableType>} parameter can be used
45+
* to customize this behavior.
4346
*
4447
* @author Sebastien Deleuze
4548
* @author Iain Henderson
@@ -54,10 +57,26 @@ public abstract class KotlinSerializationBinaryDecoder<T extends BinaryFormat> e
5457
private final ByteArrayDecoder byteArrayDecoder = new ByteArrayDecoder();
5558

5659

60+
/**
61+
* Creates a new instance with the given format and supported mime types
62+
* which only decodes types annotated with
63+
* {@link kotlinx.serialization.Serializable @Serializable} at type or
64+
* generics level.
65+
*/
5766
public KotlinSerializationBinaryDecoder(T format, MimeType... supportedMimeTypes) {
5867
super(format, supportedMimeTypes);
5968
}
6069

70+
/**
71+
* Creates a new instance with the given format and supported mime types
72+
* which only decodes types for which the specified predicate returns
73+
* {@code true}.
74+
* @since 7.0
75+
*/
76+
public KotlinSerializationBinaryDecoder(T format, Predicate<ResolvableType> typePredicate, MimeType... supportedMimeTypes) {
77+
super(format, typePredicate, supportedMimeTypes);
78+
}
79+
6180
/**
6281
* Configure a limit on the number of bytes that can be buffered whenever
6382
* the input stream needs to be aggregated. This can be a result of

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

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.List;
2020
import java.util.Map;
21+
import java.util.function.Predicate;
2122

2223
import kotlinx.serialization.BinaryFormat;
2324
import kotlinx.serialization.KSerializer;
@@ -38,9 +39,11 @@
3839
* Abstract base class for {@link Encoder} implementations that defer to Kotlin
3940
* {@linkplain BinaryFormat binary serializers}.
4041
*
41-
* <p>As of Spring Framework 7.0,
42-
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
43-
* is supported.
42+
* <p>As of Spring Framework 7.0, by default it only encodes types annotated with
43+
* {@link kotlinx.serialization.Serializable @Serializable} at type or generics level
44+
* since it allows combined usage with other general purpose encoders without conflicts.
45+
* Alternative constructors with a {@code Predicate<ResolvableType>} parameter can be used
46+
* to customize this behavior.
4447
*
4548
* @author Sebastien Deleuze
4649
* @author Iain Henderson
@@ -54,10 +57,26 @@ public abstract class KotlinSerializationBinaryEncoder<T extends BinaryFormat> e
5457
// ByteArraySequence encoding needed for now, see https://github.com/Kotlin/kotlinx.serialization/issues/204 for more details
5558
private final ByteArrayEncoder byteArrayEncoder = new ByteArrayEncoder();
5659

60+
/**
61+
* Creates a new instance with the given format and supported mime types
62+
* which only encodes types annotated with
63+
* {@link kotlinx.serialization.Serializable @Serializable} at type or
64+
* generics level.
65+
*/
5766
protected KotlinSerializationBinaryEncoder(T format, MimeType... supportedMimeTypes) {
5867
super(format, supportedMimeTypes);
5968
}
6069

70+
/**
71+
* Creates a new instance with the given format and supported mime types
72+
* which only encodes types for which the specified predicate returns
73+
* {@code true}.
74+
* @since 7.0
75+
*/
76+
protected KotlinSerializationBinaryEncoder(T format, Predicate<ResolvableType> typePredicate, MimeType... supportedMimeTypes) {
77+
super(format, typePredicate, supportedMimeTypes);
78+
}
79+
6180
@Override
6281
public boolean canEncode(ResolvableType elementType, @Nullable MimeType mimeType) {
6382
return canSerialize(elementType, mimeType);

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

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.List;
2020
import java.util.Map;
21+
import java.util.function.Predicate;
2122

2223
import kotlinx.serialization.KSerializer;
2324
import kotlinx.serialization.StringFormat;
@@ -38,9 +39,11 @@
3839
* Abstract base class for {@link Decoder} implementations that defer to Kotlin
3940
* {@linkplain StringFormat string serializers}.
4041
*
41-
* <p>As of Spring Framework 7.0,
42-
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
43-
* is supported.
42+
* <p>As of Spring Framework 7.0, by default it only decodes types annotated with
43+
* {@link kotlinx.serialization.Serializable @Serializable} at type or generics level
44+
* since it allows combined usage with other general purpose decoders without conflicts.
45+
* Alternative constructors with a {@code Predicate<ResolvableType>} parameter can be used
46+
* to customize this behavior.
4447
*
4548
* @author Sebastien Deleuze
4649
* @author Iain Henderson
@@ -55,10 +58,26 @@ public abstract class KotlinSerializationStringDecoder<T extends StringFormat> e
5558
private final StringDecoder stringDecoder = StringDecoder.allMimeTypes(StringDecoder.DEFAULT_DELIMITERS, false);
5659

5760

61+
/**
62+
* Creates a new instance with the given format and supported mime types
63+
* which only decodes types annotated with
64+
* {@link kotlinx.serialization.Serializable @Serializable} at type or
65+
* generics level.
66+
*/
5867
public KotlinSerializationStringDecoder(T format, MimeType... supportedMimeTypes) {
5968
super(format, supportedMimeTypes);
6069
}
6170

71+
/**
72+
* Creates a new instance with the given format and supported mime types
73+
* which only decodes types for which the specified predicate returns
74+
* {@code true}.
75+
* @since 7.0
76+
*/
77+
public KotlinSerializationStringDecoder(T format, Predicate<ResolvableType> typePredicate, MimeType... supportedMimeTypes) {
78+
super(format, typePredicate, supportedMimeTypes);
79+
}
80+
6281
/**
6382
* Configure a limit on the number of bytes that can be buffered whenever
6483
* the input stream needs to be aggregated. This can be a result of

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

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.List;
2323
import java.util.Map;
2424
import java.util.Set;
25+
import java.util.function.Predicate;
2526

2627
import kotlinx.serialization.KSerializer;
2728
import kotlinx.serialization.StringFormat;
@@ -43,9 +44,11 @@
4344
* Abstract base class for {@link Encoder} implementations that defer to Kotlin
4445
* {@linkplain StringFormat string serializers}.
4546
*
46-
* <p>As of Spring Framework 7.0,
47-
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
48-
* is supported.
47+
* <p>As of Spring Framework 7.0, by default it only encodes types annotated with
48+
* {@link kotlinx.serialization.Serializable @Serializable} at type or generics level
49+
* since it allows combined usage with other general purpose encoders without conflicts.
50+
* Alternative constructors with a {@code Predicate<ResolvableType>} parameter can be used
51+
* to customize this behavior.
4952
*
5053
* @author Sebastien Deleuze
5154
* @author Iain Henderson
@@ -65,10 +68,27 @@ public abstract class KotlinSerializationStringEncoder<T extends StringFormat> e
6568
private final CharSequenceEncoder charSequenceEncoder = CharSequenceEncoder.allMimeTypes();
6669
private final Set<MimeType> streamingMediaTypes = new HashSet<>();
6770

71+
72+
/**
73+
* Creates a new instance with the given format and supported mime types
74+
* which only encodes types annotated with
75+
* {@link kotlinx.serialization.Serializable @Serializable} at type or
76+
* generics level.
77+
*/
6878
protected KotlinSerializationStringEncoder(T format, MimeType... supportedMimeTypes) {
6979
super(format, supportedMimeTypes);
7080
}
7181

82+
/**
83+
* Creates a new instance with the given format and supported mime types
84+
* which only encodes types for which the specified predicate returns
85+
* {@code true}.
86+
* @since 7.0
87+
*/
88+
protected KotlinSerializationStringEncoder(T format, Predicate<ResolvableType> typePredicate, MimeType... supportedMimeTypes) {
89+
super(format, typePredicate, supportedMimeTypes);
90+
}
91+
7292
/**
7393
* Set streaming {@link MediaType MediaTypes}.
7494
* @param streamingMediaTypes streaming {@link MediaType MediaTypes}

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

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Arrays;
2222
import java.util.List;
2323
import java.util.Map;
24+
import java.util.function.Predicate;
2425

2526
import kotlin.reflect.KFunction;
2627
import kotlin.reflect.KType;
@@ -42,9 +43,11 @@
4243
* Base class providing support methods for encoding and decoding with Kotlin
4344
* serialization.
4445
*
45-
* <p>As of Spring Framework 7.0,
46-
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
47-
* is supported.
46+
* <p>As of Spring Framework 7.0, by default it only handles types annotated with
47+
* {@link kotlinx.serialization.Serializable @Serializable} at type or generics level
48+
* since it allows combined usage with other general purpose decoders without conflicts.
49+
* Alternative constructors with a {@code Predicate<ResolvableType>} parameter can be used
50+
* to customize this behavior.
4851
*
4952
* @author Sebastien Deleuze
5053
* @author Iain Henderson
@@ -63,12 +66,29 @@ public abstract class KotlinSerializationSupport<T extends SerialFormat> {
6366

6467
private final List<MimeType> supportedMimeTypes;
6568

69+
private final Predicate<ResolvableType> typePredicate;
70+
6671
/**
67-
* Creates a new instance of this support class with the given format
68-
* and supported mime types.
72+
* Creates a new instance with the given format and supported mime types
73+
* which only handle types annotated with
74+
* {@link kotlinx.serialization.Serializable @Serializable} at type or
75+
* generics level.
6976
*/
7077
protected KotlinSerializationSupport(T format, MimeType... supportedMimeTypes) {
7178
this.format = format;
79+
this.typePredicate = KotlinDetector::hasSerializableAnnotation;
80+
this.supportedMimeTypes = Arrays.asList(supportedMimeTypes);
81+
}
82+
83+
/**
84+
* Creates a new instance with the given format and supported mime types
85+
* which only encode types for which the specified predicate returns
86+
* {@code true}.
87+
* @since 7.0
88+
*/
89+
protected KotlinSerializationSupport(T format, Predicate<ResolvableType> typePredicate, MimeType... supportedMimeTypes) {
90+
this.format = format;
91+
this.typePredicate = typePredicate;
7292
this.supportedMimeTypes = Arrays.asList(supportedMimeTypes);
7393
}
7494

@@ -94,15 +114,10 @@ protected final List<MimeType> supportedMimeTypes() {
94114
* @return {@code true} if {@code type} can be serialized; false otherwise
95115
*/
96116
protected final boolean canSerialize(ResolvableType type, @Nullable MimeType mimeType) {
97-
KSerializer<Object> serializer = serializer(type);
98-
if (serializer == null) {
117+
if (!this.typePredicate.test(type) || ResolvableType.NONE.equals(type)) {
99118
return false;
100119
}
101-
else {
102-
return (supports(mimeType) && !String.class.isAssignableFrom(type.toClass()) &&
103-
!ServerSentEvent.class.isAssignableFrom(type.toClass()));
104-
}
105-
120+
return serializer(type) != null && supports(mimeType);
106121
}
107122

108123
private boolean supports(@Nullable MimeType mimeType) {

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

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@
1616

1717
package org.springframework.http.codec.cbor;
1818

19+
import java.util.function.Predicate;
20+
1921
import kotlinx.serialization.cbor.Cbor;
2022

23+
import org.springframework.core.ResolvableType;
2124
import org.springframework.http.MediaType;
2225
import org.springframework.http.codec.KotlinSerializationBinaryDecoder;
2326

@@ -26,24 +29,62 @@
2629
* <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>.
2730
* It supports {@code application/cbor}.
2831
*
29-
* <p>As of Spring Framework 7.0,
30-
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
31-
* is supported.
32+
* <p>As of Spring Framework 7.0, by default it only decodes types annotated with
33+
* {@link kotlinx.serialization.Serializable @Serializable} at type or generics
34+
* level since it allows combined usage with other general purpose CBOR decoders
35+
* like {@link JacksonCborDecoder} without conflicts.
36+
*
37+
* <p>Alternative constructors with a {@code Predicate<ResolvableType>}
38+
* parameter can be used to customize this behavior. For example,
39+
* {@code new KotlinSerializationCborDecoder(type -> true)} will decode all types
40+
* supported by Kotlin Serialization, including unannotated Kotlin enumerations,
41+
* numbers, characters, booleans and strings.
3242
*
3343
* <p>Decoding streams is not supported yet, see
3444
* <a href="https://github.com/Kotlin/kotlinx.serialization/issues/1073">kotlinx.serialization/issues/1073</a>
3545
* related issue.
3646
*
3747
* @author Iain Henderson
48+
* @author Sebastien Deleuze
3849
* @since 6.0
50+
* @see KotlinSerializationCborEncoder
3951
*/
4052
public class KotlinSerializationCborDecoder extends KotlinSerializationBinaryDecoder<Cbor> {
4153

54+
/**
55+
* Construct a new decoder using {@link Cbor.Default} instance which
56+
* only decodes types annotated with {@link kotlinx.serialization.Serializable @Serializable}
57+
* at type or generics level.
58+
*/
4259
public KotlinSerializationCborDecoder() {
4360
this(Cbor.Default);
4461
}
4562

63+
/**
64+
* Construct a new decoder using {@link Cbor.Default} instance which
65+
* only decodes types for which the specified predicate returns {@code true}.
66+
* @since 7.0
67+
*/
68+
public KotlinSerializationCborDecoder(Predicate<ResolvableType> typePredicate) {
69+
this(Cbor.Default, typePredicate);
70+
}
71+
72+
/**
73+
* Construct a new decoder using the provided {@link Cbor} instance which
74+
* only decodes types annotated with {@link kotlinx.serialization.Serializable @Serializable}
75+
* at type or generics level.
76+
*/
4677
public KotlinSerializationCborDecoder(Cbor cbor) {
4778
super(cbor, MediaType.APPLICATION_CBOR);
4879
}
80+
81+
/**
82+
* Construct a new decoder using the provided {@link Cbor} instance which
83+
* only decodes types for which the specified predicate returns {@code true}.
84+
* @since 7.0
85+
*/
86+
public KotlinSerializationCborDecoder(Cbor cbor, Predicate<ResolvableType> typePredicate) {
87+
super(cbor, typePredicate, MediaType.APPLICATION_CBOR);
88+
}
89+
4990
}

0 commit comments

Comments
 (0)