Skip to content

Commit

Permalink
Add an encodeCollection extensions (#1749)
Browse files Browse the repository at this point in the history
  • Loading branch information
ansman committed Nov 22, 2021
1 parent 471f4bb commit 53b46e9
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 15 deletions.
2 changes: 2 additions & 0 deletions core/api/kotlinx-serialization-core.api
Expand Up @@ -513,6 +513,8 @@ public final class kotlinx/serialization/encoding/Encoder$DefaultImpls {
}

public final class kotlinx/serialization/encoding/EncodingKt {
public static final fun encodeCollection (Lkotlinx/serialization/encoding/Encoder;Lkotlinx/serialization/descriptors/SerialDescriptor;ILkotlin/jvm/functions/Function1;)V
public static final fun encodeCollection (Lkotlinx/serialization/encoding/Encoder;Lkotlinx/serialization/descriptors/SerialDescriptor;Ljava/util/Collection;Lkotlin/jvm/functions/Function3;)V
public static final fun encodeStructure (Lkotlinx/serialization/encoding/Encoder;Lkotlinx/serialization/descriptors/SerialDescriptor;Lkotlin/jvm/functions/Function1;)V
}

Expand Down
29 changes: 29 additions & 0 deletions core/commonMain/src/kotlinx/serialization/encoding/Encoding.kt
Expand Up @@ -486,3 +486,32 @@ public inline fun Encoder.encodeStructure(descriptor: SerialDescriptor, block: C
if (ex == null) composite.endStructure(descriptor)
}
}

/**
* Begins a collection, encodes it using the given [block] and ends it.
*/
public inline fun Encoder.encodeCollection(
descriptor: SerialDescriptor,
collectionSize: Int,
crossinline block: CompositeEncoder.() -> Unit
) {
with(beginCollection(descriptor, collectionSize)) {
block()
endStructure(descriptor)
}
}

/**
* Begins a collection, calls [block] with each item and ends the collections.
*/
public inline fun <E> Encoder.encodeCollection(
descriptor: SerialDescriptor,
collection: Collection<E>,
crossinline block: CompositeEncoder.(index: Int, E) -> Unit
) {
encodeCollection(descriptor, collection.size) {
collection.forEachIndexed { index, e ->

This comment has been minimized.

Copy link
@slavonnet

slavonnet Nov 24, 2021

performance issues ? asSequence or reduce ?

block(index, e)
}
}
}
Expand Up @@ -63,11 +63,11 @@ internal sealed class ListLikeSerializer<Element, Collection, Builder>(

override fun serialize(encoder: Encoder, value: Collection) {
val size = value.collectionSize()
val composite = encoder.beginCollection(descriptor, size)
val iterator = value.collectionIterator()
for (index in 0 until size)
composite.encodeSerializableElement(descriptor, index, elementSerializer, iterator.next())
composite.endStructure(descriptor)
encoder.encodeCollection(descriptor, size) {
val iterator = value.collectionIterator()
for (index in 0 until size)
encodeSerializableElement(descriptor, index, elementSerializer, iterator.next())

This comment has been minimized.

Copy link
@slavonnet

slavonnet Nov 24, 2021

maybe use default value for index like
index : Int = descriptor.size.inc()
because code is seq

or if index and collection size used may be need use concurent/buffered logic?

This comment has been minimized.

Copy link
@pdvrieze

pdvrieze Nov 24, 2021

Contributor

Concurrent modification of the collection can not be supported correctly within the library/format. In particular some formats may first record the collection size before the individual elements are serialized. Formats would not normally have access to the collection (to create defensive copies). If synchronization is needed, this is something that a custom serializer could support (making defensive copies while synchronized).

}
}

protected final override fun readAll(decoder: CompositeDecoder, builder: Builder, startIndex: Int, size: Int) {
Expand Down Expand Up @@ -115,14 +115,14 @@ public sealed class MapLikeSerializer<Key, Value, Collection, Builder : MutableM

override fun serialize(encoder: Encoder, value: Collection) {
val size = value.collectionSize()
val composite = encoder.beginCollection(descriptor, size)
val iterator = value.collectionIterator()
var index = 0
iterator.forEach { (k, v) ->
composite.encodeSerializableElement(descriptor, index++, keySerializer, k)
composite.encodeSerializableElement(descriptor, index++, valueSerializer, v)
encoder.encodeCollection(descriptor, size) {
val iterator = value.collectionIterator()
var index = 0
iterator.forEach { (k, v) ->
encodeSerializableElement(descriptor, index++, keySerializer, k)
encodeSerializableElement(descriptor, index++, valueSerializer, v)

This comment has been minimized.

Copy link
@slavonnet

slavonnet Nov 24, 2021

kay and value must have equal index like row?

This comment has been minimized.

Copy link
@pdvrieze

pdvrieze Nov 24, 2021

Contributor

The way that maps are presented to a format is using interleaving. To change this would cause incompatible changes for formats. A format is free to (de)serialize this as appropriate to the format.

}
}
composite.endStructure(descriptor)
}
}

Expand Down Expand Up @@ -171,9 +171,9 @@ internal abstract class PrimitiveArraySerializer<Element, Array, Builder

final override fun serialize(encoder: Encoder, value: Array) {
val size = value.collectionSize()
val composite = encoder.beginCollection(descriptor, size)
writeContent(composite, value, size)
composite.endStructure(descriptor)
encoder.encodeCollection(descriptor, size) {
writeContent(this, value, size)
}
}

final override fun deserialize(decoder: Decoder): Array = merge(decoder, null)
Expand Down
@@ -0,0 +1,27 @@
package kotlinx.serialization

import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.*
import kotlin.test.*

class EncodingCollectionsTest {
object ListSerializer : KSerializer<List<String>> {
override val descriptor: SerialDescriptor = ListSerializer(String.serializer()).descriptor

override fun serialize(encoder: Encoder, value: List<String>) {
encoder.encodeCollection(descriptor, value) { index, item ->
encodeStringElement(descriptor, index, item)
}
}

override fun deserialize(decoder: Decoder): List<String> = throw NotImplementedError()
}

@Test
fun testEncoding() {
assertEquals("""["Hello","World!"]""", Json.encodeToString(ListSerializer, listOf("Hello", "World!")))
}
}

0 comments on commit 53b46e9

Please sign in to comment.