Skip to content

Commit d254e6d

Browse files
authored
Support serialization of collections that are not lists (Kotlin#1821)
So far there was an implicit assumption hard-coded that collections are always lists. However, sets are also collections, and can be serialized to JSON arrays just like lists. This change allows serializing generic collections independently of the concrete implementation. Fixes Kotlin#1421. * CollectionSerializers: Remove redundant visibility modifiers * SealedGenericClassesTest: Remove unused variables to avoid warnings * Rename `ListLikeSerializer` to `CollectionLikeSerializer` Now with `CollectionSerializer` inheriting from `ListLikeSerializer`, it makes sense to rename `ListLikeSerializer` to the more generic `CollectionLikeSerializer`.
1 parent 3ac9b89 commit d254e6d

File tree

4 files changed

+85
-41
lines changed

4 files changed

+85
-41
lines changed

core/api/kotlinx-serialization-core.api

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -542,13 +542,11 @@ public abstract class kotlinx/serialization/internal/AbstractPolymorphicSerializ
542542
public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
543543
}
544544

545-
public final class kotlinx/serialization/internal/ArrayListSerializer : kotlinx/serialization/internal/ListLikeSerializer {
545+
public final class kotlinx/serialization/internal/ArrayListSerializer : kotlinx/serialization/internal/CollectionSerializer {
546546
public fun <init> (Lkotlinx/serialization/KSerializer;)V
547547
public synthetic fun builder ()Ljava/lang/Object;
548548
public synthetic fun builderSize (Ljava/lang/Object;)I
549549
public synthetic fun checkCapacity (Ljava/lang/Object;I)V
550-
public synthetic fun collectionIterator (Ljava/lang/Object;)Ljava/util/Iterator;
551-
public synthetic fun collectionSize (Ljava/lang/Object;)I
552550
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
553551
public synthetic fun insert (Ljava/lang/Object;ILjava/lang/Object;)V
554552
public synthetic fun toBuilder (Ljava/lang/Object;)Ljava/lang/Object;
@@ -624,6 +622,23 @@ public final class kotlinx/serialization/internal/CharSerializer : kotlinx/seria
624622
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
625623
}
626624

625+
public abstract class kotlinx/serialization/internal/CollectionLikeSerializer : kotlinx/serialization/internal/AbstractCollectionSerializer {
626+
public synthetic fun <init> (Lkotlinx/serialization/KSerializer;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
627+
public abstract fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
628+
protected abstract fun insert (Ljava/lang/Object;ILjava/lang/Object;)V
629+
protected final fun readAll (Lkotlinx/serialization/encoding/CompositeDecoder;Ljava/lang/Object;II)V
630+
protected fun readElement (Lkotlinx/serialization/encoding/CompositeDecoder;ILjava/lang/Object;Z)V
631+
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
632+
}
633+
634+
public abstract class kotlinx/serialization/internal/CollectionSerializer : kotlinx/serialization/internal/CollectionLikeSerializer {
635+
public fun <init> (Lkotlinx/serialization/KSerializer;)V
636+
public synthetic fun collectionIterator (Ljava/lang/Object;)Ljava/util/Iterator;
637+
protected fun collectionIterator (Ljava/util/Collection;)Ljava/util/Iterator;
638+
public synthetic fun collectionSize (Ljava/lang/Object;)I
639+
protected fun collectionSize (Ljava/util/Collection;)I
640+
}
641+
627642
public final class kotlinx/serialization/internal/DoubleArrayBuilder : kotlinx/serialization/internal/PrimitiveArrayBuilder {
628643
public synthetic fun build$kotlinx_serialization_core ()Ljava/lang/Object;
629644
}
@@ -717,13 +732,11 @@ public final class kotlinx/serialization/internal/HashMapSerializer : kotlinx/se
717732
public synthetic fun toResult (Ljava/lang/Object;)Ljava/lang/Object;
718733
}
719734

720-
public final class kotlinx/serialization/internal/HashSetSerializer : kotlinx/serialization/internal/ListLikeSerializer {
735+
public final class kotlinx/serialization/internal/HashSetSerializer : kotlinx/serialization/internal/CollectionSerializer {
721736
public fun <init> (Lkotlinx/serialization/KSerializer;)V
722737
public synthetic fun builder ()Ljava/lang/Object;
723738
public synthetic fun builderSize (Ljava/lang/Object;)I
724739
public synthetic fun checkCapacity (Ljava/lang/Object;I)V
725-
public synthetic fun collectionIterator (Ljava/lang/Object;)Ljava/util/Iterator;
726-
public synthetic fun collectionSize (Ljava/lang/Object;)I
727740
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
728741
public synthetic fun insert (Ljava/lang/Object;ILjava/lang/Object;)V
729742
public synthetic fun toBuilder (Ljava/lang/Object;)Ljava/lang/Object;
@@ -788,28 +801,17 @@ public final class kotlinx/serialization/internal/LinkedHashMapSerializer : kotl
788801
public synthetic fun toResult (Ljava/lang/Object;)Ljava/lang/Object;
789802
}
790803

791-
public final class kotlinx/serialization/internal/LinkedHashSetSerializer : kotlinx/serialization/internal/ListLikeSerializer {
804+
public final class kotlinx/serialization/internal/LinkedHashSetSerializer : kotlinx/serialization/internal/CollectionSerializer {
792805
public fun <init> (Lkotlinx/serialization/KSerializer;)V
793806
public synthetic fun builder ()Ljava/lang/Object;
794807
public synthetic fun builderSize (Ljava/lang/Object;)I
795808
public synthetic fun checkCapacity (Ljava/lang/Object;I)V
796-
public synthetic fun collectionIterator (Ljava/lang/Object;)Ljava/util/Iterator;
797-
public synthetic fun collectionSize (Ljava/lang/Object;)I
798809
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
799810
public synthetic fun insert (Ljava/lang/Object;ILjava/lang/Object;)V
800811
public synthetic fun toBuilder (Ljava/lang/Object;)Ljava/lang/Object;
801812
public synthetic fun toResult (Ljava/lang/Object;)Ljava/lang/Object;
802813
}
803814

804-
public abstract class kotlinx/serialization/internal/ListLikeSerializer : kotlinx/serialization/internal/AbstractCollectionSerializer {
805-
public synthetic fun <init> (Lkotlinx/serialization/KSerializer;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
806-
public abstract fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
807-
protected abstract fun insert (Ljava/lang/Object;ILjava/lang/Object;)V
808-
protected final fun readAll (Lkotlinx/serialization/encoding/CompositeDecoder;Ljava/lang/Object;II)V
809-
protected fun readElement (Lkotlinx/serialization/encoding/CompositeDecoder;ILjava/lang/Object;Z)V
810-
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
811-
}
812-
813815
public final class kotlinx/serialization/internal/LongArrayBuilder : kotlinx/serialization/internal/PrimitiveArrayBuilder {
814816
public synthetic fun build$kotlinx_serialization_core ()Ljava/lang/Object;
815817
}
@@ -935,7 +937,7 @@ public class kotlinx/serialization/internal/PluginGeneratedSerialDescriptor : ko
935937
public abstract class kotlinx/serialization/internal/PrimitiveArrayBuilder {
936938
}
937939

938-
public abstract class kotlinx/serialization/internal/PrimitiveArraySerializer : kotlinx/serialization/internal/ListLikeSerializer {
940+
public abstract class kotlinx/serialization/internal/PrimitiveArraySerializer : kotlinx/serialization/internal/CollectionLikeSerializer {
939941
public synthetic fun builder ()Ljava/lang/Object;
940942
protected final fun builder ()Lkotlinx/serialization/internal/PrimitiveArrayBuilder;
941943
public synthetic fun builderSize (Ljava/lang/Object;)I
@@ -955,7 +957,7 @@ public abstract class kotlinx/serialization/internal/PrimitiveArraySerializer :
955957
protected abstract fun writeContent (Lkotlinx/serialization/encoding/CompositeEncoder;Ljava/lang/Object;I)V
956958
}
957959

958-
public final class kotlinx/serialization/internal/ReferenceArraySerializer : kotlinx/serialization/internal/ListLikeSerializer {
960+
public final class kotlinx/serialization/internal/ReferenceArraySerializer : kotlinx/serialization/internal/CollectionLikeSerializer {
959961
public fun <init> (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V
960962
public synthetic fun builder ()Ljava/lang/Object;
961963
public synthetic fun builderSize (Ljava/lang/Object;)I

core/commonMain/src/kotlinx/serialization/internal/CollectionSerializers.kt

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public sealed class AbstractCollectionSerializer<Element, Collection, Builder> :
5454
}
5555

5656
@PublishedApi
57-
internal sealed class ListLikeSerializer<Element, Collection, Builder>(
57+
internal sealed class CollectionLikeSerializer<Element, Collection, Builder>(
5858
private val elementSerializer: KSerializer<Element>
5959
) : AbstractCollectionSerializer<Element, Collection, Builder>() {
6060

@@ -70,13 +70,13 @@ internal sealed class ListLikeSerializer<Element, Collection, Builder>(
7070
}
7171
}
7272

73-
protected final override fun readAll(decoder: CompositeDecoder, builder: Builder, startIndex: Int, size: Int) {
73+
final override fun readAll(decoder: CompositeDecoder, builder: Builder, startIndex: Int, size: Int) {
7474
require(size >= 0) { "Size must be known in advance when using READ_ALL" }
7575
for (index in 0 until size)
7676
readElement(decoder, startIndex + index, builder, checkIndex = false)
7777
}
7878

79-
protected override fun readElement(decoder: CompositeDecoder, index: Int, builder: Builder, checkIndex: Boolean) {
79+
override fun readElement(decoder: CompositeDecoder, index: Int, builder: Builder, checkIndex: Boolean) {
8080
builder.insert(index, decoder.decodeSerializableElement(descriptor, index, elementSerializer))
8181
}
8282
}
@@ -143,7 +143,7 @@ internal abstract class PrimitiveArrayBuilder<Array> internal constructor() {
143143
internal abstract class PrimitiveArraySerializer<Element, Array, Builder
144144
: PrimitiveArrayBuilder<Array>> internal constructor(
145145
primitiveSerializer: KSerializer<Element>
146-
) : ListLikeSerializer<Element, Array, Builder>(primitiveSerializer) {
146+
) : CollectionLikeSerializer<Element, Array, Builder>(primitiveSerializer) {
147147
final override val descriptor: SerialDescriptor = PrimitiveArrayDescriptor(primitiveSerializer.descriptor)
148148

149149
final override fun Builder.builderSize(): Int = position
@@ -160,7 +160,7 @@ internal abstract class PrimitiveArraySerializer<Element, Array, Builder
160160

161161
protected abstract fun empty(): Array
162162

163-
protected abstract override fun readElement(
163+
abstract override fun readElement(
164164
decoder: CompositeDecoder,
165165
index: Int,
166166
builder: Builder,
@@ -184,7 +184,7 @@ internal abstract class PrimitiveArraySerializer<Element, Array, Builder
184184
internal class ReferenceArraySerializer<ElementKlass : Any, Element : ElementKlass?>(
185185
private val kClass: KClass<ElementKlass>,
186186
eSerializer: KSerializer<Element>
187-
) : ListLikeSerializer<Element, Array<Element>, ArrayList<Element>>(eSerializer) {
187+
) : CollectionLikeSerializer<Element, Array<Element>, ArrayList<Element>>(eSerializer) {
188188
override val descriptor: SerialDescriptor = ArrayClassDesc(eSerializer.descriptor)
189189

190190
override fun Array<Element>.collectionSize(): Int = size
@@ -202,12 +202,17 @@ internal class ReferenceArraySerializer<ElementKlass : Any, Element : ElementKla
202202
}
203203
}
204204

205+
@PublishedApi
206+
internal abstract class CollectionSerializer<E, C: Collection<E>, B>(element: KSerializer<E>) : CollectionLikeSerializer<E, C, B>(element) {
207+
override fun C.collectionSize(): Int = size
208+
override fun C.collectionIterator(): Iterator<E> = iterator()
209+
}
210+
205211
@InternalSerializationApi
206212
@PublishedApi
207-
internal class ArrayListSerializer<E>(element: KSerializer<E>) : ListLikeSerializer<E, List<E>, ArrayList<E>>(element) {
213+
internal class ArrayListSerializer<E>(element: KSerializer<E>) : CollectionSerializer<E, List<E>, ArrayList<E>>(element) {
208214
override val descriptor: SerialDescriptor = ArrayListClassDesc(element.descriptor)
209-
override fun List<E>.collectionSize(): Int = size
210-
override fun List<E>.collectionIterator(): Iterator<E> = iterator()
215+
211216
override fun builder(): ArrayList<E> = arrayListOf()
212217
override fun ArrayList<E>.builderSize(): Int = size
213218
override fun ArrayList<E>.toResult(): List<E> = this
@@ -219,11 +224,9 @@ internal class ArrayListSerializer<E>(element: KSerializer<E>) : ListLikeSeriali
219224
@PublishedApi
220225
internal class LinkedHashSetSerializer<E>(
221226
eSerializer: KSerializer<E>
222-
) : ListLikeSerializer<E, Set<E>, LinkedHashSet<E>>(eSerializer) {
223-
227+
) : CollectionSerializer<E, Set<E>, LinkedHashSet<E>>(eSerializer) {
224228
override val descriptor: SerialDescriptor = LinkedHashSetClassDesc(eSerializer.descriptor)
225-
override fun Set<E>.collectionSize(): Int = size
226-
override fun Set<E>.collectionIterator(): Iterator<E> = iterator()
229+
227230
override fun builder(): LinkedHashSet<E> = linkedSetOf()
228231
override fun LinkedHashSet<E>.builderSize(): Int = size
229232
override fun LinkedHashSet<E>.toResult(): Set<E> = this
@@ -235,11 +238,9 @@ internal class LinkedHashSetSerializer<E>(
235238
@PublishedApi
236239
internal class HashSetSerializer<E>(
237240
eSerializer: KSerializer<E>
238-
) : ListLikeSerializer<E, Set<E>, HashSet<E>>(eSerializer) {
239-
241+
) : CollectionSerializer<E, Set<E>, HashSet<E>>(eSerializer) {
240242
override val descriptor: SerialDescriptor = HashSetClassDesc(eSerializer.descriptor)
241-
override fun Set<E>.collectionSize(): Int = size
242-
override fun Set<E>.collectionIterator(): Iterator<E> = iterator()
243+
243244
override fun builder(): HashSet<E> = HashSet()
244245
override fun HashSet<E>.builderSize(): Int = size
245246
override fun HashSet<E>.toResult(): Set<E> = this

core/commonTest/src/kotlinx/serialization/SealedGenericClassesTest.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ class SealedGenericClassesTest {
3131
// Test that compilation and retrieval is successful
3232
@Test
3333
fun testQuery() {
34-
val serial1 = Query.SimpleQuery.serializer(String.serializer())
35-
val serial2 = Query.serializer(UnitSerializer)
34+
Query.SimpleQuery.serializer(String.serializer())
35+
Query.serializer(UnitSerializer)
3636
}
3737

3838
@Test
3939
fun testFetcher() {
40-
val serial1 = Fetcher.SomethingFetcher.serializer()
41-
val serial2 = Fetcher.serializer(Something.serializer())
40+
Fetcher.SomethingFetcher.serializer()
41+
Fetcher.serializer(Something.serializer())
4242
}
4343
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.serialization.features
6+
7+
import kotlinx.serialization.*
8+
import kotlinx.serialization.builtins.*
9+
import kotlinx.serialization.json.Json
10+
import kotlinx.serialization.test.*
11+
import kotlin.test.*
12+
13+
class CollectionSerializerTest {
14+
15+
@Serializable
16+
data class CollectionWrapper(
17+
val collection: Collection<String>
18+
)
19+
20+
@Test
21+
fun testListJson() {
22+
val list = listOf("foo", "bar", "foo", "bar")
23+
24+
val string = Json.encodeToString(CollectionWrapper(list))
25+
assertEquals("""{"collection":["foo","bar","foo","bar"]}""", string)
26+
27+
val wrapper = Json.decodeFromString<CollectionWrapper>(string)
28+
assertEquals(list, wrapper.collection)
29+
}
30+
31+
@Test
32+
fun testSetJson() {
33+
val set = setOf("foo", "bar", "foo", "bar")
34+
35+
val string = Json.encodeToString(CollectionWrapper(set))
36+
assertEquals("""{"collection":["foo","bar"]}""", string)
37+
38+
val wrapper = Json.decodeFromString<CollectionWrapper>(string)
39+
assertEquals(set.toList(), wrapper.collection)
40+
}
41+
}

0 commit comments

Comments
 (0)