Skip to content

Commit

Permalink
Support serialization of collections that are not lists (#1821)
Browse files Browse the repository at this point in the history
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 #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`.

(cherry picked from commit d254e6d)
  • Loading branch information
sschuberth authored and sandwwraith committed Jan 28, 2022
1 parent 2e960d0 commit a27c365
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 41 deletions.
42 changes: 22 additions & 20 deletions core/api/kotlinx-serialization-core.api
Expand Up @@ -542,13 +542,11 @@ public abstract class kotlinx/serialization/internal/AbstractPolymorphicSerializ
public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
}

public final class kotlinx/serialization/internal/ArrayListSerializer : kotlinx/serialization/internal/ListLikeSerializer {
public final class kotlinx/serialization/internal/ArrayListSerializer : kotlinx/serialization/internal/CollectionSerializer {
public fun <init> (Lkotlinx/serialization/KSerializer;)V
public synthetic fun builder ()Ljava/lang/Object;
public synthetic fun builderSize (Ljava/lang/Object;)I
public synthetic fun checkCapacity (Ljava/lang/Object;I)V
public synthetic fun collectionIterator (Ljava/lang/Object;)Ljava/util/Iterator;
public synthetic fun collectionSize (Ljava/lang/Object;)I
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun insert (Ljava/lang/Object;ILjava/lang/Object;)V
public synthetic fun toBuilder (Ljava/lang/Object;)Ljava/lang/Object;
Expand Down Expand Up @@ -624,6 +622,23 @@ public final class kotlinx/serialization/internal/CharSerializer : kotlinx/seria
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
}

public abstract class kotlinx/serialization/internal/CollectionLikeSerializer : kotlinx/serialization/internal/AbstractCollectionSerializer {
public synthetic fun <init> (Lkotlinx/serialization/KSerializer;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public abstract fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
protected abstract fun insert (Ljava/lang/Object;ILjava/lang/Object;)V
protected final fun readAll (Lkotlinx/serialization/encoding/CompositeDecoder;Ljava/lang/Object;II)V
protected fun readElement (Lkotlinx/serialization/encoding/CompositeDecoder;ILjava/lang/Object;Z)V
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
}

public abstract class kotlinx/serialization/internal/CollectionSerializer : kotlinx/serialization/internal/CollectionLikeSerializer {
public fun <init> (Lkotlinx/serialization/KSerializer;)V
public synthetic fun collectionIterator (Ljava/lang/Object;)Ljava/util/Iterator;
protected fun collectionIterator (Ljava/util/Collection;)Ljava/util/Iterator;
public synthetic fun collectionSize (Ljava/lang/Object;)I
protected fun collectionSize (Ljava/util/Collection;)I
}

public final class kotlinx/serialization/internal/DoubleArrayBuilder : kotlinx/serialization/internal/PrimitiveArrayBuilder {
public synthetic fun build$kotlinx_serialization_core ()Ljava/lang/Object;
}
Expand Down Expand Up @@ -717,13 +732,11 @@ public final class kotlinx/serialization/internal/HashMapSerializer : kotlinx/se
public synthetic fun toResult (Ljava/lang/Object;)Ljava/lang/Object;
}

public final class kotlinx/serialization/internal/HashSetSerializer : kotlinx/serialization/internal/ListLikeSerializer {
public final class kotlinx/serialization/internal/HashSetSerializer : kotlinx/serialization/internal/CollectionSerializer {
public fun <init> (Lkotlinx/serialization/KSerializer;)V
public synthetic fun builder ()Ljava/lang/Object;
public synthetic fun builderSize (Ljava/lang/Object;)I
public synthetic fun checkCapacity (Ljava/lang/Object;I)V
public synthetic fun collectionIterator (Ljava/lang/Object;)Ljava/util/Iterator;
public synthetic fun collectionSize (Ljava/lang/Object;)I
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun insert (Ljava/lang/Object;ILjava/lang/Object;)V
public synthetic fun toBuilder (Ljava/lang/Object;)Ljava/lang/Object;
Expand Down Expand Up @@ -788,28 +801,17 @@ public final class kotlinx/serialization/internal/LinkedHashMapSerializer : kotl
public synthetic fun toResult (Ljava/lang/Object;)Ljava/lang/Object;
}

public final class kotlinx/serialization/internal/LinkedHashSetSerializer : kotlinx/serialization/internal/ListLikeSerializer {
public final class kotlinx/serialization/internal/LinkedHashSetSerializer : kotlinx/serialization/internal/CollectionSerializer {
public fun <init> (Lkotlinx/serialization/KSerializer;)V
public synthetic fun builder ()Ljava/lang/Object;
public synthetic fun builderSize (Ljava/lang/Object;)I
public synthetic fun checkCapacity (Ljava/lang/Object;I)V
public synthetic fun collectionIterator (Ljava/lang/Object;)Ljava/util/Iterator;
public synthetic fun collectionSize (Ljava/lang/Object;)I
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun insert (Ljava/lang/Object;ILjava/lang/Object;)V
public synthetic fun toBuilder (Ljava/lang/Object;)Ljava/lang/Object;
public synthetic fun toResult (Ljava/lang/Object;)Ljava/lang/Object;
}

public abstract class kotlinx/serialization/internal/ListLikeSerializer : kotlinx/serialization/internal/AbstractCollectionSerializer {
public synthetic fun <init> (Lkotlinx/serialization/KSerializer;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public abstract fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
protected abstract fun insert (Ljava/lang/Object;ILjava/lang/Object;)V
protected final fun readAll (Lkotlinx/serialization/encoding/CompositeDecoder;Ljava/lang/Object;II)V
protected fun readElement (Lkotlinx/serialization/encoding/CompositeDecoder;ILjava/lang/Object;Z)V
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
}

public final class kotlinx/serialization/internal/LongArrayBuilder : kotlinx/serialization/internal/PrimitiveArrayBuilder {
public synthetic fun build$kotlinx_serialization_core ()Ljava/lang/Object;
}
Expand Down Expand Up @@ -935,7 +937,7 @@ public class kotlinx/serialization/internal/PluginGeneratedSerialDescriptor : ko
public abstract class kotlinx/serialization/internal/PrimitiveArrayBuilder {
}

public abstract class kotlinx/serialization/internal/PrimitiveArraySerializer : kotlinx/serialization/internal/ListLikeSerializer {
public abstract class kotlinx/serialization/internal/PrimitiveArraySerializer : kotlinx/serialization/internal/CollectionLikeSerializer {
public synthetic fun builder ()Ljava/lang/Object;
protected final fun builder ()Lkotlinx/serialization/internal/PrimitiveArrayBuilder;
public synthetic fun builderSize (Ljava/lang/Object;)I
Expand All @@ -955,7 +957,7 @@ public abstract class kotlinx/serialization/internal/PrimitiveArraySerializer :
protected abstract fun writeContent (Lkotlinx/serialization/encoding/CompositeEncoder;Ljava/lang/Object;I)V
}

public final class kotlinx/serialization/internal/ReferenceArraySerializer : kotlinx/serialization/internal/ListLikeSerializer {
public final class kotlinx/serialization/internal/ReferenceArraySerializer : kotlinx/serialization/internal/CollectionLikeSerializer {
public fun <init> (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V
public synthetic fun builder ()Ljava/lang/Object;
public synthetic fun builderSize (Ljava/lang/Object;)I
Expand Down
Expand Up @@ -54,7 +54,7 @@ public sealed class AbstractCollectionSerializer<Element, Collection, Builder> :
}

@PublishedApi
internal sealed class ListLikeSerializer<Element, Collection, Builder>(
internal sealed class CollectionLikeSerializer<Element, Collection, Builder>(
private val elementSerializer: KSerializer<Element>
) : AbstractCollectionSerializer<Element, Collection, Builder>() {

Expand All @@ -70,13 +70,13 @@ internal sealed class ListLikeSerializer<Element, Collection, Builder>(
}
}

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

protected override fun readElement(decoder: CompositeDecoder, index: Int, builder: Builder, checkIndex: Boolean) {
override fun readElement(decoder: CompositeDecoder, index: Int, builder: Builder, checkIndex: Boolean) {
builder.insert(index, decoder.decodeSerializableElement(descriptor, index, elementSerializer))
}
}
Expand Down Expand Up @@ -143,7 +143,7 @@ internal abstract class PrimitiveArrayBuilder<Array> internal constructor() {
internal abstract class PrimitiveArraySerializer<Element, Array, Builder
: PrimitiveArrayBuilder<Array>> internal constructor(
primitiveSerializer: KSerializer<Element>
) : ListLikeSerializer<Element, Array, Builder>(primitiveSerializer) {
) : CollectionLikeSerializer<Element, Array, Builder>(primitiveSerializer) {
final override val descriptor: SerialDescriptor = PrimitiveArrayDescriptor(primitiveSerializer.descriptor)

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

protected abstract fun empty(): Array

protected abstract override fun readElement(
abstract override fun readElement(
decoder: CompositeDecoder,
index: Int,
builder: Builder,
Expand All @@ -184,7 +184,7 @@ internal abstract class PrimitiveArraySerializer<Element, Array, Builder
internal class ReferenceArraySerializer<ElementKlass : Any, Element : ElementKlass?>(
private val kClass: KClass<ElementKlass>,
eSerializer: KSerializer<Element>
) : ListLikeSerializer<Element, Array<Element>, ArrayList<Element>>(eSerializer) {
) : CollectionLikeSerializer<Element, Array<Element>, ArrayList<Element>>(eSerializer) {
override val descriptor: SerialDescriptor = ArrayClassDesc(eSerializer.descriptor)

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

@PublishedApi
internal abstract class CollectionSerializer<E, C: Collection<E>, B>(element: KSerializer<E>) : CollectionLikeSerializer<E, C, B>(element) {
override fun C.collectionSize(): Int = size
override fun C.collectionIterator(): Iterator<E> = iterator()
}

@InternalSerializationApi
@PublishedApi
internal class ArrayListSerializer<E>(element: KSerializer<E>) : ListLikeSerializer<E, List<E>, ArrayList<E>>(element) {
internal class ArrayListSerializer<E>(element: KSerializer<E>) : CollectionSerializer<E, List<E>, ArrayList<E>>(element) {
override val descriptor: SerialDescriptor = ArrayListClassDesc(element.descriptor)
override fun List<E>.collectionSize(): Int = size
override fun List<E>.collectionIterator(): Iterator<E> = iterator()

override fun builder(): ArrayList<E> = arrayListOf()
override fun ArrayList<E>.builderSize(): Int = size
override fun ArrayList<E>.toResult(): List<E> = this
Expand All @@ -219,11 +224,9 @@ internal class ArrayListSerializer<E>(element: KSerializer<E>) : ListLikeSeriali
@PublishedApi
internal class LinkedHashSetSerializer<E>(
eSerializer: KSerializer<E>
) : ListLikeSerializer<E, Set<E>, LinkedHashSet<E>>(eSerializer) {

) : CollectionSerializer<E, Set<E>, LinkedHashSet<E>>(eSerializer) {
override val descriptor: SerialDescriptor = LinkedHashSetClassDesc(eSerializer.descriptor)
override fun Set<E>.collectionSize(): Int = size
override fun Set<E>.collectionIterator(): Iterator<E> = iterator()

override fun builder(): LinkedHashSet<E> = linkedSetOf()
override fun LinkedHashSet<E>.builderSize(): Int = size
override fun LinkedHashSet<E>.toResult(): Set<E> = this
Expand All @@ -235,11 +238,9 @@ internal class LinkedHashSetSerializer<E>(
@PublishedApi
internal class HashSetSerializer<E>(
eSerializer: KSerializer<E>
) : ListLikeSerializer<E, Set<E>, HashSet<E>>(eSerializer) {

) : CollectionSerializer<E, Set<E>, HashSet<E>>(eSerializer) {
override val descriptor: SerialDescriptor = HashSetClassDesc(eSerializer.descriptor)
override fun Set<E>.collectionSize(): Int = size
override fun Set<E>.collectionIterator(): Iterator<E> = iterator()

override fun builder(): HashSet<E> = HashSet()
override fun HashSet<E>.builderSize(): Int = size
override fun HashSet<E>.toResult(): Set<E> = this
Expand Down
Expand Up @@ -31,13 +31,13 @@ class SealedGenericClassesTest {
// Test that compilation and retrieval is successful
@Test
fun testQuery() {
val serial1 = Query.SimpleQuery.serializer(String.serializer())
val serial2 = Query.serializer(UnitSerializer)
Query.SimpleQuery.serializer(String.serializer())
Query.serializer(UnitSerializer)
}

@Test
fun testFetcher() {
val serial1 = Fetcher.SomethingFetcher.serializer()
val serial2 = Fetcher.serializer(Something.serializer())
Fetcher.SomethingFetcher.serializer()
Fetcher.serializer(Something.serializer())
}
}
@@ -0,0 +1,41 @@
/*
* Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.serialization.features

import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.test.*
import kotlin.test.*

class CollectionSerializerTest {

@Serializable
data class CollectionWrapper(
val collection: Collection<String>
)

@Test
fun testListJson() {
val list = listOf("foo", "bar", "foo", "bar")

val string = Json.encodeToString(CollectionWrapper(list))
assertEquals("""{"collection":["foo","bar","foo","bar"]}""", string)

val wrapper = Json.decodeFromString<CollectionWrapper>(string)
assertEquals(list, wrapper.collection)
}

@Test
fun testSetJson() {
val set = setOf("foo", "bar", "foo", "bar")

val string = Json.encodeToString(CollectionWrapper(set))
assertEquals("""{"collection":["foo","bar"]}""", string)

val wrapper = Json.decodeFromString<CollectionWrapper>(string)
assertEquals(set.toList(), wrapper.collection)
}
}

0 comments on commit a27c365

Please sign in to comment.