diff options
author | Leonid Startsev <sandwwraith@gmail.com> | 2022-10-13 17:55:28 +0200 |
---|---|---|
committer | Leonid Startsev <sandwwraith@gmail.com> | 2022-10-13 17:55:28 +0200 |
commit | de6864af7039ab91dac4f0764af94c96da018914 (patch) | |
tree | 92c5590d771465b4fab342a669d6ac09c180b579 | |
parent | 0a1b6d856da3bc9e6a19f77ad66c1b241533a20b (diff) | |
parent | daa95c79ffadc0eedbbb4a481a00556b78212e43 (diff) | |
download | kotlinx.serialization-de6864af7039ab91dac4f0764af94c96da018914.tar.gz |
Merge remote-tracking branch 'origin/master' into dev
5 files changed, 217 insertions, 6 deletions
diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/ContextualOverheadBenchmark.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/ContextualOverheadBenchmark.kt new file mode 100644 index 00000000..2773cfc2 --- /dev/null +++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/ContextualOverheadBenchmark.kt @@ -0,0 +1,72 @@ +package kotlinx.benchmarks.json + +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.descriptors.element +import kotlinx.serialization.encoding.* +import kotlinx.serialization.json.* +import kotlinx.serialization.modules.* +import org.openjdk.jmh.annotations.* +import java.util.concurrent.* + +@Warmup(iterations = 7, time = 1) +@Measurement(iterations = 5, time = 1) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Benchmark) +@Fork(1) +open class ContextualOverheadBenchmark { + @Serializable + data class Holder(val data: @Contextual Data) + + class Data(val a: Int, val b: String) + + object DataSerializer: KSerializer<Data> { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Serializer") { + element<Int>("a") + element<String>("b") + } + + override fun deserialize(decoder: Decoder): Data { + return decoder.decodeStructure(descriptor) { + var a = 0 + var b = "" + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> a = decodeIntElement(descriptor, 0) + 1 -> b = decodeStringElement(descriptor, 1) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + Data(a, b) + } + } + + override fun serialize(encoder: Encoder, value: Data) { + encoder.encodeStructure(descriptor) { + encodeIntElement(descriptor, 0, value.a) + encodeStringElement(descriptor, 1, value.b) + } + } + + } + + private val module = SerializersModule { + contextual(DataSerializer) + } + + private val json = Json { serializersModule = module } + + private val holder = Holder(Data(1, "abc")) + private val holderString = json.encodeToString(holder) + private val holderSerializer = serializer<Holder>() + + @Benchmark + fun decode() = json.decodeFromString(holderSerializer, holderString) + + @Benchmark + fun encode() = json.encodeToString(holderSerializer, holder) + +} diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/PolymorphismOverheadBenchmark.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/PolymorphismOverheadBenchmark.kt index b272bae6..9af856d3 100644 --- a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/PolymorphismOverheadBenchmark.kt +++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/PolymorphismOverheadBenchmark.kt @@ -19,6 +19,9 @@ open class PolymorphismOverheadBenchmark { data class PolymorphicWrapper(val i: @Polymorphic Poly, val i2: Impl) // amortize the cost a bit @Serializable + data class SimpleWrapper(val poly: @Polymorphic Poly) + + @Serializable data class BaseWrapper(val i: Impl, val i2: Impl) @JsonClassDiscriminator("poly") @@ -40,6 +43,11 @@ open class PolymorphismOverheadBenchmark { private val polyString = json.encodeToString<Poly>(impl) private val serializer = serializer<Poly>() + private val wrapper = SimpleWrapper(Impl(1, "abc")) + private val wrapperString = json.encodeToString(wrapper) + private val wrapperSerializer = serializer<SimpleWrapper>() + + // 5000 @Benchmark fun base() = json.decodeFromString(Impl.serializer(), implString) @@ -51,4 +59,12 @@ open class PolymorphismOverheadBenchmark { @Benchmark fun poly() = json.decodeFromString(serializer, polyString) + // test for child polymorphic serializer in decoding + @Benchmark + fun polyChildDecode() = json.decodeFromString(wrapperSerializer, wrapperString) + + // test for child polymorphic serializer in encoding + @Benchmark + fun polyChildEncode() = json.encodeToString(wrapperSerializer, wrapper) + } diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/UseSerializerOverheadBenchmark.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/UseSerializerOverheadBenchmark.kt new file mode 100644 index 00000000..bc3c89d7 --- /dev/null +++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/UseSerializerOverheadBenchmark.kt @@ -0,0 +1,123 @@ +@file:UseSerializers(UseSerializerOverheadBenchmark.DataClassSerializer::class, UseSerializerOverheadBenchmark.DataObjectSerializer::class) +package kotlinx.benchmarks.json + + +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.descriptors.element +import kotlinx.serialization.encoding.* +import kotlinx.serialization.json.* +import kotlinx.serialization.modules.* +import org.openjdk.jmh.annotations.* +import java.util.concurrent.* + +@Warmup(iterations = 7, time = 1) +@Measurement(iterations = 5, time = 1) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Benchmark) +@Fork(1) +open class UseSerializerOverheadBenchmark { + @Serializable + data class HolderForClass(val data: DataForClass) + + @Serializable + data class HolderForObject(val data: DataForObject) + + class DataForClass(val a: Int, val b: String) + + class DataForObject(val a: Int, val b: String) + + object DataClassSerializer: KSerializer<DataForClass> { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ClassSerializer") { + element<Int>("a") + element<String>("b") + } + + override fun deserialize(decoder: Decoder): DataForClass { + return decoder.decodeStructure(descriptor) { + var a = 0 + var b = "" + while (true) { + when (val index = decodeElementIndex(ContextualOverheadBenchmark.DataSerializer.descriptor)) { + 0 -> a = decodeIntElement(ContextualOverheadBenchmark.DataSerializer.descriptor, 0) + 1 -> b = decodeStringElement(ContextualOverheadBenchmark.DataSerializer.descriptor, 1) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + DataForClass(a, b) + } + } + + override fun serialize(encoder: Encoder, value: DataForClass) { + encoder.encodeStructure(descriptor) { + encodeIntElement(descriptor, 0, value.a) + encodeStringElement(descriptor, 1, value.b) + } + } + } + + object DataObjectSerializer: KSerializer<DataForObject> { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ObjectSerializer") { + element<Int>("a") + element<String>("b") + } + + override fun deserialize(decoder: Decoder): DataForObject { + return decoder.decodeStructure(descriptor) { + var a = 0 + var b = "" + while (true) { + when (val index = decodeElementIndex(ContextualOverheadBenchmark.DataSerializer.descriptor)) { + 0 -> a = decodeIntElement(ContextualOverheadBenchmark.DataSerializer.descriptor, 0) + 1 -> b = decodeStringElement(ContextualOverheadBenchmark.DataSerializer.descriptor, 1) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + DataForObject(a, b) + } + } + + override fun serialize(encoder: Encoder, value: DataForObject) { + encoder.encodeStructure(descriptor) { + encodeIntElement(descriptor, 0, value.a) + encodeStringElement(descriptor, 1, value.b) + } + } + } + + private val module = SerializersModule { + contextual(DataClassSerializer) + } + + private val json = Json { serializersModule = module } + + private val classHolder = HolderForClass(DataForClass(1, "abc")) + private val classHolderString = json.encodeToString(classHolder) + private val classHolderSerializer = serializer<HolderForClass>() + + private val objectHolder = HolderForObject(DataForObject(1, "abc")) + private val objectHolderString = json.encodeToString(objectHolder) + private val objectHolderSerializer = serializer<HolderForObject>() + + @Benchmark + fun decodeForClass() = json.decodeFromString(classHolderSerializer, classHolderString) + + @Benchmark + fun encodeForClass() = json.encodeToString(classHolderSerializer, classHolder) + + /* + Any optimizations should not affect the speed of these tests. + It doesn't make sense to cache singleton (`object`) serializer, because the object is accessed instantly + */ + + @Benchmark + fun decodeForObject() = json.decodeFromString(objectHolderSerializer, objectHolderString) + + @Benchmark + fun encodeForObject() = json.encodeToString(objectHolderSerializer, objectHolder) + +} diff --git a/docs/polymorphism.md b/docs/polymorphism.md index f49c0ed6..486c7489 100644 --- a/docs/polymorphism.md +++ b/docs/polymorphism.md @@ -41,11 +41,11 @@ Let us start with basic introduction to polymorphism. ### Static types -Kotlin serialization is fully static with respect to types by default. The structure of encoded objects is determined +Kotlin Serialization is fully static with respect to types by default. The structure of encoded objects is determined by *compile-time* types of objects. Let's examine this aspect in more detail and learn how to serialize polymorphic data structures, where the type of data is determined at runtime. -To show the static nature of Kotlin serialization let us make the following setup. An `open class Project` +To show the static nature of Kotlin Serialization let us make the following setup. An `open class Project` has just the `name` property, while its derived `class OwnedProject` adds an `owner` property. In the below example, we serialize `data` variable with a static type of `Project` that is initialized with an instance of `OwnedProject` at runtime. @@ -194,7 +194,7 @@ discriminator property is not emitted into the resulting JSON. <!--- TEST --> -In general, Kotlin serialization is designed to work correctly only when the compile-time type used during serialization +In general, Kotlin Serialization is designed to work correctly only when the compile-time type used during serialization is the same one as the compile-time type used during deserialization. You can always specify the type explicitly when calling serialization functions. The previous example can be corrected to use `Project` type for serialization by calling `Json.encodeToString<Project>(data)`. @@ -592,7 +592,7 @@ With the explicit serializer it works as before. ### Explicitly marking polymorphic class properties The property of an interface type is implicitly considered polymorphic, since interfaces are all about runtime polymorphism. -However, Kotlin serialization does not compile a serializable class with a property of a non-serializable class type. +However, Kotlin Serialization does not compile a serializable class with a property of a non-serializable class type. If we have a property of `Any` class or other non-serializable class, then we must explicitly provide its serialization strategy via the [`@Serializable`][Serializable] annotation as we saw in the [Specifying serializer on a property](serializers.md#specifying-serializer-on-a-property) section. @@ -708,7 +708,7 @@ abstract class Response<out T> data class OkResponse<out T>(val data: T) : Response<T>() ``` -Kotlin serialization does not have a builtin strategy to represent the actually provided argument type for the +Kotlin Serialization does not have a builtin strategy to represent the actually provided argument type for the type parameter `T` when serializing a property of the polymorphic type `OkResponse<T>`. We have to provide this strategy explicitly when defining the serializers module for the `Response`. In the below example we use `OkResponse.serializer(...)` to retrieve diff --git a/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt b/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt index 4bd46d59..e5815838 100644 --- a/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt +++ b/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt @@ -257,7 +257,7 @@ private class DynamicPrimitiveEncoder( if (!json.configuration.isLenient && abs(value) > MAX_SAFE_INTEGER) { throw IllegalArgumentException( "$value can't be deserialized to number due to a potential precision loss. " + - "Use the JsonConfiguration option isLenient to serialise anyway" + "Use the JsonConfiguration option isLenient to serialize anyway" ) } encodeValue(asDouble) |