Merge remote-tracking branch 'origin/master' into dev
diff --git a/build.gradle b/build.gradle
index 73b566a..983e791 100644
--- a/build.gradle
+++ b/build.gradle
@@ -39,6 +39,10 @@
]
ext.koverEnabled = property('kover.enabled') ?: true
+ def noTeamcityInteractionFlag = rootProject.hasProperty("no_teamcity_interaction")
+ def buildSnapshotUPFlag = rootProject.hasProperty("build_snapshot_up")
+ ext.teamcityInteractionDisabled = noTeamcityInteractionFlag || buildSnapshotUPFlag
+
/*
* This property group is used to build kotlinx.serialization against Kotlin compiler snapshot.
* When build_snapshot_train is set to true, kotlin_version property is overridden with kotlin_snapshot_version.
diff --git a/buildSrc/src/main/kotlin/Java9Modularity.kt b/buildSrc/src/main/kotlin/Java9Modularity.kt
index 73621d8..e8c41fc 100644
--- a/buildSrc/src/main/kotlin/Java9Modularity.kt
+++ b/buildSrc/src/main/kotlin/Java9Modularity.kt
@@ -12,6 +12,7 @@
import org.gradle.kotlin.dsl.*
import org.gradle.language.base.plugins.LifecycleBasePlugin.*
import org.gradle.process.*
+import org.jetbrains.kotlin.gradle.*
import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.mpp.*
@@ -128,7 +129,10 @@
kotlinOptions {
moduleName = compileTask.kotlinOptions.moduleName
jvmTarget = "9"
- freeCompilerArgs += "-Xjdk-release=9"
+ // To support LV override when set in aggregate builds
+ languageVersion = compileTask.kotlinOptions.languageVersion
+ freeCompilerArgs += listOf("-Xjdk-release=9", "-Xsuppress-version-warnings", "-Xexpect-actual-classes")
+ options.optIn.addAll(compileTask.kotlinOptions.options.optIn)
}
// work-around for https://youtrack.jetbrains.com/issue/KT-60583
inputs.files(
@@ -147,6 +151,11 @@
// part of work-around for https://youtrack.jetbrains.com/issue/KT-60541
@Suppress("INVISIBLE_MEMBER")
commonSourceSet.from(compileTask.commonSourceSet)
+ @OptIn(InternalKotlinGradlePluginApi::class)
+ apply {
+ multiplatformStructure.refinesEdges.set(compileTask.multiplatformStructure.refinesEdges)
+ multiplatformStructure.fragments.set(compileTask.multiplatformStructure.fragments)
+ }
// part of work-around for https://youtrack.jetbrains.com/issue/KT-60541
// and work-around for https://youtrack.jetbrains.com/issue/KT-60582
incremental = false
diff --git a/buildSrc/src/main/kotlin/KotlinVersion.kt b/buildSrc/src/main/kotlin/KotlinVersion.kt
new file mode 100644
index 0000000..5ac051e
--- /dev/null
+++ b/buildSrc/src/main/kotlin/KotlinVersion.kt
@@ -0,0 +1,14 @@
+@file:JvmName("KotlinVersion")
+
+fun isKotlinVersionAtLeast(kotlinVersion: String, atLeastMajor: Int, atLeastMinor: Int, atLeastPatch: Int): Boolean {
+ val (major, minor) = kotlinVersion
+ .split('.')
+ .take(2)
+ .map { it.toInt() }
+ val patch = kotlinVersion.substringAfterLast('.').substringBefore('-').toInt()
+ return when {
+ major > atLeastMajor -> true
+ major < atLeastMajor -> false
+ else -> (minor == atLeastMinor && patch >= atLeastPatch) || minor > atLeastMinor
+ }
+}
diff --git a/core/build.gradle b/core/build.gradle
index a561b02..f52837a 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -66,3 +66,7 @@
}
Java9Modularity.configureJava9ModuleInfo(project)
+
+tasks.withType(org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrLink.class).configureEach {
+ kotlinOptions.freeCompilerArgs += "-Xwasm-enable-array-range-checks"
+}
diff --git a/core/commonMain/src/kotlinx/serialization/SerializationExceptions.kt b/core/commonMain/src/kotlinx/serialization/SerializationExceptions.kt
index 7af9d9a..99f7d0a 100644
--- a/core/commonMain/src/kotlinx/serialization/SerializationExceptions.kt
+++ b/core/commonMain/src/kotlinx/serialization/SerializationExceptions.kt
@@ -5,6 +5,7 @@
package kotlinx.serialization
import kotlinx.serialization.encoding.*
+import kotlinx.serialization.descriptors.*
/**
* A generic exception indicating the problem in serialization or deserialization process.
@@ -65,6 +66,12 @@
* [MissingFieldException] is thrown on missing field from all [auto-generated][Serializable] serializers and it
* is recommended to throw this exception from user-defined serializers.
*
+ * [MissingFieldException] is constructed from the following properties:
+ * - [missingFields] -- fields that were required for the deserialization but have not been found.
+ * They are always non-empty and their names match the corresponding names in [SerialDescriptor.elementNames]
+ * - Optional `serialName` -- serial name of the enclosing class that failed to get deserialized.
+ * Matches the corresponding [SerialDescriptor.serialName].
+ *
* @see SerializationException
* @see KSerializer
*/
diff --git a/core/commonMain/src/kotlinx/serialization/encoding/AbstractDecoder.kt b/core/commonMain/src/kotlinx/serialization/encoding/AbstractDecoder.kt
index fad7ef8..ffe6dd3 100644
--- a/core/commonMain/src/kotlinx/serialization/encoding/AbstractDecoder.kt
+++ b/core/commonMain/src/kotlinx/serialization/encoding/AbstractDecoder.kt
@@ -74,8 +74,7 @@
index: Int,
deserializer: DeserializationStrategy<T?>,
previousValue: T?
- ): T? {
- val isNullabilitySupported = deserializer.descriptor.isNullable
- return if (isNullabilitySupported || decodeNotNullMark()) decodeSerializableValue(deserializer, previousValue) else decodeNull()
+ ): T? = decodeIfNullable(deserializer) {
+ decodeSerializableValue(deserializer, previousValue)
}
}
diff --git a/core/commonMain/src/kotlinx/serialization/encoding/Decoding.kt b/core/commonMain/src/kotlinx/serialization/encoding/Decoding.kt
index f29c805..dc4aa2a 100644
--- a/core/commonMain/src/kotlinx/serialization/encoding/Decoding.kt
+++ b/core/commonMain/src/kotlinx/serialization/encoding/Decoding.kt
@@ -260,12 +260,17 @@
* Decodes the nullable value of type [T] by delegating the decoding process to the given [deserializer].
*/
@ExperimentalSerializationApi
- public fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): T? {
- val isNullabilitySupported = deserializer.descriptor.isNullable
- return if (isNullabilitySupported || decodeNotNullMark()) decodeSerializableValue(deserializer) else decodeNull()
+ public fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): T? = decodeIfNullable(deserializer) {
+ decodeSerializableValue(deserializer)
}
}
+@OptIn(ExperimentalSerializationApi::class)
+internal inline fun <T : Any> Decoder.decodeIfNullable(deserializer: DeserializationStrategy<T?>, block: () -> T?): T? {
+ val isNullabilitySupported = deserializer.descriptor.isNullable
+ return if (isNullabilitySupported || decodeNotNullMark()) block() else decodeNull()
+}
+
/**
* [CompositeDecoder] is a part of decoding process that is bound to a particular structured part of
* the serialized form, described by the serial descriptor passed to [Decoder.beginStructure].
diff --git a/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt b/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt
index 8604bbc..26d3b5e 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt
@@ -58,8 +58,8 @@
}
else -> throw SerializationException(
"Invalid index in polymorphic deserialization of " +
- (klassName ?: "unknown class") +
- "\n Expected 0, 1 or DECODE_DONE(-1), but found $index"
+ (klassName ?: "unknown class") +
+ "\n Expected 0, 1 or DECODE_DONE(-1), but found $index"
)
}
}
@@ -98,14 +98,14 @@
@JvmName("throwSubtypeNotRegistered")
internal fun throwSubtypeNotRegistered(subClassName: String?, baseClass: KClass<*>): Nothing {
- val scope = "in the scope of '${baseClass.simpleName}'"
+ val scope = "in the polymorphic scope of '${baseClass.simpleName}'"
throw SerializationException(
if (subClassName == null)
- "Class discriminator was missing and no default polymorphic serializers were registered $scope"
+ "Class discriminator was missing and no default serializers were registered $scope."
else
- "Class '$subClassName' is not registered for polymorphic serialization $scope.\n" +
- "To be registered automatically, class '$subClassName' has to be '@Serializable', and the base class '${baseClass.simpleName}' has to be sealed and '@Serializable'.\n" +
- "Alternatively, register the serializer for '$subClassName' explicitly in a corresponding SerializersModule."
+ "Serializer for subclass '$subClassName' is not found $scope.\n" +
+ "Check if class with serial name '$subClassName' exists and serializer is registered in a corresponding SerializersModule.\n" +
+ "To be registered automatically, class '$subClassName' has to be '@Serializable', and the base class '${baseClass.simpleName}' has to be sealed and '@Serializable'."
)
}
diff --git a/core/commonMain/src/kotlinx/serialization/internal/ElementMarker.kt b/core/commonMain/src/kotlinx/serialization/internal/ElementMarker.kt
index fca9026..5e6736d 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/ElementMarker.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/ElementMarker.kt
@@ -4,13 +4,13 @@
package kotlinx.serialization.internal
-import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.*
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.CompositeDecoder
@OptIn(ExperimentalSerializationApi::class)
-@PublishedApi
-internal class ElementMarker(
+@CoreFriendModuleApi
+public class ElementMarker(
private val descriptor: SerialDescriptor,
// Instead of inheritance and virtual function in order to keep cross-module internal modifier via suppresses
// Can be reworked via public + internal api if necessary
@@ -45,7 +45,7 @@
}
}
- fun mark(index: Int) {
+ public fun mark(index: Int) {
if (index < Long.SIZE_BITS) {
lowerMarks = lowerMarks or (1L shl index)
} else {
@@ -53,7 +53,7 @@
}
}
- fun nextUnmarkedIndex(): Int {
+ public fun nextUnmarkedIndex(): Int {
val elementsCount = descriptor.elementsCount
while (lowerMarks != -1L) {
val index = lowerMarks.inv().countTrailingZeroBits()
diff --git a/core/commonMain/src/kotlinx/serialization/internal/JsonInternalDependencies.kt b/core/commonMain/src/kotlinx/serialization/internal/JsonInternalDependencies.kt
index d79cb8b..e733827 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/JsonInternalDependencies.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/JsonInternalDependencies.kt
@@ -1,14 +1,14 @@
package kotlinx.serialization.internal
-import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
/*
- * Methods that are required for kotlinx-serialization-json, but are not effectively public
- * and actually represent our own technical debt.
- * This methods are not intended for public use
+ * Methods that are required for kotlinx-serialization-json, but are not effectively public.
+ *
+ * Anything marker with this annotation is not intended for public use.
*/
+@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
+internal annotation class CoreFriendModuleApi
-@InternalSerializationApi
-@Deprecated(message = "Should not be used", level = DeprecationLevel.ERROR)
+@CoreFriendModuleApi
public fun SerialDescriptor.jsonCachedSerialNames(): Set<String> = cachedSerialNames()
diff --git a/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt b/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt
index 7b6efe7..a954bda 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt
@@ -1,7 +1,7 @@
/*
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-@file:Suppress("OPTIONAL_DECLARATION_USAGE_IN_NON_COMMON_SOURCE", "UNUSED")
+@file:Suppress("UNUSED")
package kotlinx.serialization.internal
diff --git a/core/commonMain/src/kotlinx/serialization/internal/Tagged.kt b/core/commonMain/src/kotlinx/serialization/internal/Tagged.kt
index 3af5d35..cf71388 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/Tagged.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/Tagged.kt
@@ -206,7 +206,6 @@
protected open fun <T : Any?> decodeSerializableValue(deserializer: DeserializationStrategy<T>, previousValue: T?): T =
decodeSerializableValue(deserializer)
-
// ---- Implementation of low-level API ----
override fun decodeInline(descriptor: SerialDescriptor): Decoder =
@@ -284,13 +283,11 @@
index: Int,
deserializer: DeserializationStrategy<T?>,
previousValue: T?
- ): T? =
- tagBlock(descriptor.getTag(index)) {
- if (decodeNotNullMark()) decodeSerializableValue(
- deserializer,
- previousValue
- ) else decodeNull()
+ ): T? = tagBlock(descriptor.getTag(index)) {
+ decodeIfNullable(deserializer) {
+ decodeSerializableValue(deserializer, previousValue)
}
+ }
private fun <E> tagBlock(tag: Tag, block: () -> E): E {
pushTag(tag)
diff --git a/core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt b/core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt
index 455a896..caa2768 100644
--- a/core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt
+++ b/core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt
@@ -10,6 +10,7 @@
import kotlinx.serialization.encoding.*
import kotlinx.serialization.encoding.CompositeDecoder.Companion.UNKNOWN_NAME
import kotlinx.serialization.modules.*
+import kotlinx.serialization.test.*
import kotlin.test.*
import kotlin.time.Duration
diff --git a/core/commonTest/src/kotlinx/serialization/ElementMarkerTest.kt b/core/commonTest/src/kotlinx/serialization/ElementMarkerTest.kt
index a22be3f..3fee587 100644
--- a/core/commonTest/src/kotlinx/serialization/ElementMarkerTest.kt
+++ b/core/commonTest/src/kotlinx/serialization/ElementMarkerTest.kt
@@ -2,10 +2,11 @@
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.CompositeDecoder
-import kotlinx.serialization.internal.ElementMarker
+import kotlinx.serialization.internal.*
import kotlin.test.Test
import kotlin.test.assertEquals
+@OptIn(CoreFriendModuleApi::class)
class ElementMarkerTest {
@Test
fun testNothingWasRead() {
diff --git a/core/commonTest/src/kotlinx/serialization/SerializersLookupInterfaceTest.kt b/core/commonTest/src/kotlinx/serialization/SerializersLookupInterfaceTest.kt
index 15c0351..fc77057 100644
--- a/core/commonTest/src/kotlinx/serialization/SerializersLookupInterfaceTest.kt
+++ b/core/commonTest/src/kotlinx/serialization/SerializersLookupInterfaceTest.kt
@@ -34,7 +34,7 @@
@Test
fun testInterfaceLookup() {
// Native does not have KClass.isInterface
- if (isNative()) return
+ if (isNative() || isWasm()) return
val serializer1 = serializer<I>()
assertTrue(serializer1 is PolymorphicSerializer)
diff --git a/core/commonTest/src/kotlinx/serialization/SerializersLookupNamedCompanionTest.kt b/core/commonTest/src/kotlinx/serialization/SerializersLookupNamedCompanionTest.kt
index 6c429db..65324c4 100644
--- a/core/commonTest/src/kotlinx/serialization/SerializersLookupNamedCompanionTest.kt
+++ b/core/commonTest/src/kotlinx/serialization/SerializersLookupNamedCompanionTest.kt
@@ -64,25 +64,25 @@
fun test() {
assertSame<KSerializer<*>>(Plain.serializer(), serializer(typeOf<Plain>()))
- shouldFail<SerializationException>(beforeKotlin = "1.9.20", onJs = false, onNative = false) {
+ shouldFail<SerializationException>(beforeKotlin = "1.9.20", onJs = false, onNative = false, onWasm = false) {
assertSame<KSerializer<*>>(PlainSerializer, serializer(typeOf<PlainWithCustom>()))
}
- shouldFail<SerializationException>(beforeKotlin = "1.9.20", onJs = false, onNative = false) {
+ shouldFail<SerializationException>(beforeKotlin = "1.9.20", onJs = false, onNative = false, onWasm = false) {
assertEquals(
Parametrized.serializer(Int.serializer()).descriptor.toString(),
serializer(typeOf<Parametrized<Int>>()).descriptor.toString()
)
}
- shouldFail<SerializationException>(beforeKotlin = "1.9.20", onJs = false, onNative = false) {
+ shouldFail<SerializationException>(beforeKotlin = "1.9.20", onJs = false, onNative = false, onWasm = false) {
assertEquals(
ParametrizedWithCustom.serializer(Int.serializer()).descriptor.toString(),
serializer(typeOf<ParametrizedWithCustom<Int>>()).descriptor.toString()
)
}
- shouldFail<SerializationException>(beforeKotlin = "1.9.20", onJs = false, onNative = false) {
+ shouldFail<SerializationException>(beforeKotlin = "1.9.20", onJs = false, onNative = false, onWasm = false) {
assertEquals(
SealedInterface.serializer().descriptor.toString(),
serializer(typeOf<SealedInterface>()).descriptor.toString()
@@ -91,7 +91,7 @@
// should fail because annotation @NamedCompanion will be placed again by the compilation plugin
// and they both will be placed into @Container annotation - thus they will be invisible to the runtime
- shouldFail<SerializationException>(sinceKotlin = "1.9.20", onJs = false, onNative = false) {
+ shouldFail<SerializationException>(sinceKotlin = "1.9.20", onJs = false, onNative = false, onWasm = false) {
serializer(typeOf<SealedInterfaceWithExplicitAnnotation>())
}
}
diff --git a/core/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt b/core/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt
index 24e4c52..ce9de63 100644
--- a/core/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt
+++ b/core/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt
@@ -86,4 +86,4 @@
arrayOf(null, -1, -2),
arrayOf(IntData(1), IntData(2))
)
-)
+)
\ No newline at end of file
diff --git a/core/commonTest/src/kotlinx/serialization/test/CompilerVersions.kt b/core/commonTest/src/kotlinx/serialization/test/CompilerVersions.kt
index e9c7670..7bd35c1 100644
--- a/core/commonTest/src/kotlinx/serialization/test/CompilerVersions.kt
+++ b/core/commonTest/src/kotlinx/serialization/test/CompilerVersions.kt
@@ -29,6 +29,7 @@
onJvm: Boolean = true,
onJs: Boolean = true,
onNative: Boolean = true,
+ onWasm: Boolean = true,
test: () -> Unit
) {
val args = mapOf(
@@ -36,7 +37,8 @@
"before" to beforeKotlin,
"onJvm" to onJvm,
"onJs" to onJs,
- "onNative" to onNative
+ "onNative" to onNative,
+ "onWasm" to onWasm
)
val sinceVersion = sinceKotlin?.toKotlinVersion()
@@ -45,7 +47,7 @@
val version = (sinceVersion != null && currentKotlinVersion >= sinceVersion)
|| (beforeVersion != null && currentKotlinVersion < beforeVersion)
- val platform = (isJvm() && onJvm) || (isJs() && onJs) || (isNative() && onNative)
+ val platform = (isJvm() && onJvm) || (isJs() && onJs) || (isNative() && onNative) || (isWasm() && onWasm)
var error: Throwable? = null
try {
@@ -164,6 +166,16 @@
shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0", beforeKotlin = "255.255.255", onJs = false) {
// no-op
}
+ } else if (isWasm()) {
+ shouldFail<IllegalArgumentException>(beforeKotlin = "255.255.255", onWasm = false) {
+ // no-op
+ }
+ shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0", onWasm = false) {
+ // no-op
+ }
+ shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0", beforeKotlin = "255.255.255", onWasm = false) {
+ // no-op
+ }
} else if (isNative()) {
shouldFail<IllegalArgumentException>(beforeKotlin = "255.255.255", onNative = false) {
// no-op
diff --git a/core/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt b/core/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt
index 92cf087..594ec0b 100644
--- a/core/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt
+++ b/core/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt
@@ -5,7 +5,7 @@
package kotlinx.serialization.test
enum class Platform {
- JVM, JS, NATIVE
+ JVM, JS, NATIVE, WASM
}
public expect val currentPlatform: Platform
@@ -13,3 +13,4 @@
public fun isJs(): Boolean = currentPlatform == Platform.JS
public fun isJvm(): Boolean = currentPlatform == Platform.JVM
public fun isNative(): Boolean = currentPlatform == Platform.NATIVE
+public fun isWasm(): Boolean = currentPlatform == Platform.WASM
diff --git a/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt b/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt
index 399dbb2..72ec9ea 100644
--- a/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt
+++ b/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt
@@ -153,6 +153,9 @@
}
private fun <T : Any> Class<T>.findObjectSerializer(): KSerializer<T>? {
+ // Special case to avoid IllegalAccessException on Java11+ (#2449)
+ // There are no serializable objects in the stdlib anyway.
+ if (this.canonicalName?.let { it.startsWith("java.") || it.startsWith("kotlin.") } != false) return null
// Check it is an object without using kotlin-reflect
val field =
declaredFields.singleOrNull { it.name == "INSTANCE" && it.type == this && Modifier.isStatic(it.modifiers) }
diff --git a/core/wasmMain/src/kotlinx/serialization/Serializers.kt b/core/wasmMain/src/kotlinx/serialization/Serializers.kt
new file mode 100644
index 0000000..a499392
--- /dev/null
+++ b/core/wasmMain/src/kotlinx/serialization/Serializers.kt
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization
+
+import kotlin.reflect.*
+
+@OptIn(ExperimentalAssociatedObjects::class)
+@AssociatedObjectKey
+@Retention(AnnotationRetention.BINARY)
+@PublishedApi
+internal annotation class SerializableWith(public val serializer: KClass<out KSerializer<*>>)
\ No newline at end of file
diff --git a/core/wasmMain/src/kotlinx/serialization/internal/Platform.kt b/core/wasmMain/src/kotlinx/serialization/internal/Platform.kt
new file mode 100644
index 0000000..310df02
--- /dev/null
+++ b/core/wasmMain/src/kotlinx/serialization/internal/Platform.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.internal
+
+import kotlinx.serialization.*
+import kotlin.reflect.*
+
+@Suppress("NOTHING_TO_INLINE")
+internal actual inline fun <T> Array<T>.getChecked(index: Int): T {
+ return get(index)
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal actual inline fun BooleanArray.getChecked(index: Int): Boolean {
+ return get(index)
+}
+
+internal actual fun KClass<*>.platformSpecificSerializerNotRegistered(): Nothing {
+ throw SerializationException(
+ "${notRegisteredMessage()}\n" +
+ "On Kotlin/Wasm explicitly declared serializer should be used for interfaces and enums without @Serializable annotation"
+ )
+}
+
+@Suppress(
+ "UNCHECKED_CAST",
+ "DEPRECATION_ERROR"
+)
+@OptIn(ExperimentalAssociatedObjects::class)
+internal actual fun <T : Any> KClass<T>.constructSerializerForGivenTypeArgs(vararg args: KSerializer<Any?>): KSerializer<T>? =
+ when (val assocObject = findAssociatedObject<SerializableWith>()) {
+ is KSerializer<*> -> assocObject as KSerializer<T>
+ is SerializerFactory -> assocObject.serializer(*args) as KSerializer<T>
+ else -> null
+ }
+
+@Suppress("DEPRECATION_ERROR")
+internal actual fun <T : Any> KClass<T>.compiledSerializerImpl(): KSerializer<T>? =
+ this.constructSerializerForGivenTypeArgs()
+
+
+internal actual fun <T> createCache(factory: (KClass<*>) -> KSerializer<T>?): SerializerCache<T> {
+ return object: SerializerCache<T> {
+ override fun get(key: KClass<Any>): KSerializer<T>? {
+ return factory(key)
+ }
+ }
+}
+
+internal actual fun <T> createParametrizedCache(factory: (KClass<Any>, List<KType>) -> KSerializer<T>?): ParametrizedSerializerCache<T> {
+ return object: ParametrizedSerializerCache<T> {
+ override fun get(key: KClass<Any>, types: List<KType>): Result<KSerializer<T>?> {
+ return kotlin.runCatching { factory(key, types) }
+ }
+ }
+}
+
+internal actual fun <T : Any, E : T?> ArrayList<E>.toNativeArrayImpl(eClass: KClass<T>): Array<E> = toTypedArray()
+
+internal actual fun isReferenceArray(rootClass: KClass<Any>): Boolean = rootClass == Array::class
\ No newline at end of file
diff --git a/core/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt b/core/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt
new file mode 100644
index 0000000..fd359b7
--- /dev/null
+++ b/core/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt
@@ -0,0 +1,7 @@
+/*
+ * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.test
+
+public actual val currentPlatform: Platform = Platform.WASM
\ No newline at end of file
diff --git a/docs/polymorphism.md b/docs/polymorphism.md
index 29d023b..b7ea31f 100644
--- a/docs/polymorphism.md
+++ b/docs/polymorphism.md
@@ -123,9 +123,9 @@
This is close to the best design for a serializable hierarchy of classes, but running it produces the following error:
```text
-Exception in thread "main" kotlinx.serialization.SerializationException: Class 'OwnedProject' is not registered for polymorphic serialization in the scope of 'Project'.
+Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for subclass 'OwnedProject' is not found in the polymorphic scope of 'Project'.
+Check if class with serial name 'OwnedProject' exists and serializer is registered in a corresponding SerializersModule.
To be registered automatically, class 'OwnedProject' has to be '@Serializable', and the base class 'Project' has to be sealed and '@Serializable'.
-Alternatively, register the serializer for 'OwnedProject' explicitly in a corresponding SerializersModule.
```
<!--- TEST LINES_START -->
@@ -832,7 +832,8 @@
We get the following exception.
```text
-Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Polymorphic serializer was not found for class discriminator 'unknown'
+Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 0: Serializer for subclass 'unknown' is not found in the polymorphic scope of 'Project' at path: $
+Check if class with serial name 'unknown' exists and serializer is registered in a corresponding SerializersModule.
```
<!--- TEST LINES_START -->
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoding.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoding.kt
index 0b7a0e0..b77a18c 100644
--- a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoding.kt
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoding.kt
@@ -70,6 +70,8 @@
if (encodeByteArrayAsByteString && serializer.descriptor == ByteArraySerializer().descriptor) {
encoder.encodeByteString(value as ByteArray)
} else {
+ encodeByteArrayAsByteString = encodeByteArrayAsByteString || serializer.descriptor.isInlineByteString()
+
super.encodeSerializableValue(serializer, value)
}
}
@@ -278,6 +280,7 @@
@Suppress("UNCHECKED_CAST")
decoder.nextByteString() as T
} else {
+ decodeByteArrayAsByteString = decodeByteArrayAsByteString || deserializer.descriptor.isInlineByteString()
super.decodeSerializableValue(deserializer)
}
}
@@ -636,6 +639,11 @@
return getElementAnnotations(index).find { it is ByteString } != null
}
+private fun SerialDescriptor.isInlineByteString(): Boolean {
+ // inline item classes should only have 1 item
+ return isInline && isByteString(0)
+}
+
private val normalizeBaseBits = SINGLE_PRECISION_NORMALIZE_BASE.toBits()
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborReaderTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborReaderTest.kt
index edbe5e6..f615d5e 100644
--- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborReaderTest.kt
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborReaderTest.kt
@@ -634,6 +634,34 @@
}
@Test
+ fun testReadValueClassWithByteString() {
+ assertContentEquals(
+ expected = byteArrayOf(0x11, 0x22, 0x33),
+ actual = Cbor.decodeFromHexString<ValueClassWithByteString>("43112233").x
+ )
+ }
+
+ @Test
+ fun testReadValueClassCustomByteString() {
+ assertEquals(
+ expected = ValueClassWithCustomByteString(CustomByteString(0x11, 0x22, 0x33)),
+ actual = Cbor.decodeFromHexString("43112233")
+ )
+ }
+
+ @Test
+ fun testReadValueClassWithUnlabeledByteString() {
+ assertContentEquals(
+ expected = byteArrayOf(
+ 0x11,
+ 0x22,
+ 0x33
+ ),
+ actual = Cbor.decodeFromHexString<ValueClassWithUnlabeledByteString>("43112233").x.x
+ )
+ }
+
+ @Test
fun testIgnoresTagsOnStrings() {
/*
* 84 # array(4)
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt
index c546bdf..da7b128 100644
--- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt
@@ -122,4 +122,28 @@
actual = Cbor.encodeToHexString(TypeWithNullableCustomByteString(null))
)
}
+
+ @Test
+ fun testWriteValueClassWithByteString() {
+ assertEquals(
+ expected = "43112233",
+ actual = Cbor.encodeToHexString(ValueClassWithByteString(byteArrayOf(0x11, 0x22, 0x33)))
+ )
+ }
+
+ @Test
+ fun testWriteValueClassCustomByteString() {
+ assertEquals(
+ expected = "43112233",
+ actual = Cbor.encodeToHexString(ValueClassWithCustomByteString(CustomByteString(0x11, 0x22, 0x33)))
+ )
+ }
+
+ @Test
+ fun testWriteValueClassWithUnlabeledByteString() {
+ assertEquals(
+ expected = "43112233",
+ actual = Cbor.encodeToHexString(ValueClassWithUnlabeledByteString(ValueClassWithUnlabeledByteString.Inner(byteArrayOf(0x11, 0x22, 0x33))))
+ )
+ }
}
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt
index ad55d04..e4418f4 100644
--- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt
@@ -8,6 +8,7 @@
import kotlinx.serialization.builtins.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
+import kotlin.jvm.*
@Serializable
data class Simple(val a: String)
@@ -110,4 +111,20 @@
data class TypeWithCustomByteString(@ByteString val x: CustomByteString)
@Serializable
-data class TypeWithNullableCustomByteString(@ByteString val x: CustomByteString?)
\ No newline at end of file
+data class TypeWithNullableCustomByteString(@ByteString val x: CustomByteString?)
+
+@JvmInline
+@Serializable
+value class ValueClassWithByteString(@ByteString val x: ByteArray)
+
+@JvmInline
+@Serializable
+value class ValueClassWithCustomByteString(@ByteString val x: CustomByteString)
+
+@JvmInline
+@Serializable
+value class ValueClassWithUnlabeledByteString(@ByteString val x: Inner) {
+ @JvmInline
+ @Serializable
+ value class Inner(val x: ByteArray)
+}
\ No newline at end of file
diff --git a/formats/json-okio/build.gradle.kts b/formats/json-okio/build.gradle.kts
index 8d29e2e..a51fff0 100644
--- a/formats/json-okio/build.gradle.kts
+++ b/formats/json-okio/build.gradle.kts
@@ -15,6 +15,12 @@
kotlin {
sourceSets {
+ configureEach {
+ languageSettings {
+ optIn("kotlinx.serialization.internal.CoreFriendModuleApi")
+ optIn("kotlinx.serialization.json.internal.JsonFriendModuleApi")
+ }
+ }
val commonMain by getting {
dependencies {
api(project(":kotlinx-serialization-core"))
@@ -44,3 +50,13 @@
}
}
}
+
+
+// TODO: Remove this after okio will be updated to the version with 1.9.20 stdlib dependency
+configurations.all {
+ resolutionStrategy.eachDependency {
+ if (requested.name == "kotlin-stdlib-wasm") {
+ useTarget("org.jetbrains.kotlin:kotlin-stdlib-wasm-js:${requested.version}")
+ }
+ }
+}
diff --git a/formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/OkioStreams.kt b/formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/OkioStreams.kt
index b827806..968f533 100644
--- a/formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/OkioStreams.kt
+++ b/formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/OkioStreams.kt
@@ -2,8 +2,6 @@
* Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
-
package kotlinx.serialization.json.okio
import kotlinx.serialization.*
@@ -29,7 +27,7 @@
) {
val writer = JsonToOkioStreamWriter(sink)
try {
- encodeByWriter(writer, serializer, value)
+ encodeByWriter(this, writer, serializer, value)
} finally {
writer.release()
}
@@ -62,7 +60,7 @@
deserializer: DeserializationStrategy<T>,
source: BufferedSource
): T {
- return decodeByReader(deserializer, OkioSerialReader(source))
+ return decodeByReader(this, deserializer, OkioSerialReader(source))
}
/**
@@ -101,7 +99,7 @@
deserializer: DeserializationStrategy<T>,
format: DecodeSequenceMode = DecodeSequenceMode.AUTO_DETECT
): Sequence<T> {
- return decodeToSequenceByReader(OkioSerialReader(source), deserializer, format)
+ return decodeToSequenceByReader(this, OkioSerialReader(source), deserializer, format)
}
/**
diff --git a/formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/internal/OkioJsonStreams.kt b/formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/internal/OkioJsonStreams.kt
index c206678..1de8971 100644
--- a/formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/internal/OkioJsonStreams.kt
+++ b/formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/internal/OkioJsonStreams.kt
@@ -2,16 +2,39 @@
* Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "CANNOT_OVERRIDE_INVISIBLE_MEMBER")
-
package kotlinx.serialization.json.okio.internal
-import kotlinx.serialization.json.internal.ESCAPE_STRINGS
-import kotlinx.serialization.json.internal.JsonWriter
-import kotlinx.serialization.json.internal.SerialReader
+import kotlinx.serialization.json.internal.*
import okio.*
-internal class JsonToOkioStreamWriter(private val sink: BufferedSink) : JsonWriter {
+// Copied from kotlinx/serialization/json/internal/StringOps.kt
+private fun toHexChar(i: Int) : Char {
+ val d = i and 0xf
+ return if (d < 10) (d + '0'.code).toChar()
+ else (d - 10 + 'a'.code).toChar()
+}
+
+// Copied from kotlinx/serialization/json/internal/StringOps.kt
+private val ESCAPE_STRINGS: Array<String?> = arrayOfNulls<String>(93).apply {
+ for (c in 0..0x1f) {
+ val c1 = toHexChar(c shr 12)
+ val c2 = toHexChar(c shr 8)
+ val c3 = toHexChar(c shr 4)
+ val c4 = toHexChar(c)
+ this[c] = "\\u$c1$c2$c3$c4"
+ }
+ this['"'.code] = "\\\""
+ this['\\'.code] = "\\\\"
+ this['\t'.code] = "\\t"
+ this['\b'.code] = "\\b"
+ this['\n'.code] = "\\n"
+ this['\r'.code] = "\\r"
+ this[0x0c] = "\\f"
+}
+
+
+
+internal class JsonToOkioStreamWriter(private val sink: BufferedSink) : InternalJsonWriter {
override fun writeLong(value: Long) {
write(value.toString())
}
@@ -54,7 +77,7 @@
private const val LOW_SURROGATE_HEADER = 0xdc00
-internal class OkioSerialReader(private val source: BufferedSource): SerialReader {
+internal class OkioSerialReader(private val source: BufferedSource): InternalJsonReader {
/*
A sequence of code points is read from UTF-8, some of it can take 2 characters.
In case the last code point requires 2 characters, and the array is already full, we buffer the second character
diff --git a/formats/json-tests/build.gradle.kts b/formats/json-tests/build.gradle.kts
index 9ae4247..6be0a3a 100644
--- a/formats/json-tests/build.gradle.kts
+++ b/formats/json-tests/build.gradle.kts
@@ -2,7 +2,7 @@
* Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
import Java9Modularity.configureJava9ModuleInfo
-import org.jetbrains.kotlin.gradle.tasks.*
+import org.jetbrains.kotlin.gradle.targets.js.testing.*
plugins {
kotlin("multiplatform")
@@ -25,6 +25,12 @@
kotlin {
sourceSets {
+ configureEach {
+ languageSettings {
+ optIn("kotlinx.serialization.internal.CoreFriendModuleApi")
+ optIn("kotlinx.serialization.json.internal.JsonFriendModuleApi")
+ }
+ }
val commonTest by getting {
dependencies {
api(project(":kotlinx-serialization-json"))
@@ -43,3 +49,12 @@
}
project.configureJava9ModuleInfo()
+
+// TODO: Remove this after okio will be updated to the version with 1.9.20 stdlib dependency
+configurations.all {
+ resolutionStrategy.eachDependency {
+ if (requested.name == "kotlin-stdlib-wasm") {
+ useTarget("org.jetbrains.kotlin:kotlin-stdlib-wasm-js:${requested.version}")
+ }
+ }
+}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt
index 52ab0f2..f878c63 100644
--- a/formats/json-tests/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt
@@ -84,3 +84,24 @@
arrayOf(IntData(1), IntData(2))
)
)
+
+val umbrellaInstance2 = TypesUmbrella(
+ Unit, true, 10, 20, 30, 40, 50.5f, 60.5, 'A', "Str0", Attitude.POSITIVE, IntData(70),
+ null, null, 11, 21, 31, 41, 51.5f, 61.5, 'B', "Str1", Attitude.NEUTRAL, null,
+ listOf(1, 2, 3),
+ listOf(4, 5, null),
+ setOf(6, 7, 8),
+ mutableSetOf(null, 9, 10),
+ listOf(listOf(Attitude.NEGATIVE, null)),
+ listOf(IntData(1), IntData(2), IntData(3)),
+ mutableListOf(IntData(1), null, IntData(3)),
+ Tree("root", Tree("left"), Tree("right", Tree("right.left"), Tree("right.right"))),
+ mapOf("one" to 1, "two" to 2, "three" to 3),
+ mapOf(0 to null, 1 to "first", 2 to "second"),
+ ArraysUmbrella(
+ arrayOf(1, 2, 3),
+ arrayOf(100, 200, 300),
+ arrayOf(null, -1, -2),
+ arrayOf(IntData(1), IntData(2))
+ )
+)
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphicDeserializationErrorMessagesTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphicDeserializationErrorMessagesTest.kt
new file mode 100644
index 0000000..2b2f1f7
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphicDeserializationErrorMessagesTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2017-2023 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.json.*
+import kotlin.test.*
+
+class PolymorphicDeserializationErrorMessagesTest : JsonTestBase() {
+ @Serializable
+ class DummyData(@Polymorphic val a: Any)
+
+ @Serializable
+ class Holder(val d: DummyData)
+
+ // TODO: remove this after #2480 is merged
+ private fun checkSerializationException(action: () -> Unit, assertions: SerializationException.(String) -> Unit) {
+ val e = assertFailsWith(SerializationException::class, action)
+ assertNotNull(e.message)
+ e.assertions(e.message!!)
+ }
+
+ @Test
+ fun testNotRegisteredMessage() = parametrizedTest { mode ->
+ val input = """{"d":{"a":{"type":"my.Class", "value":42}}}"""
+ checkSerializationException({
+ default.decodeFromString<Holder>(input, mode)
+ }, { message ->
+ // ReaderJsonLexer.peekLeadingMatchingValue is not implemented, so first-key optimization is not working for streaming yet.
+ if (mode == JsonTestingMode.STREAMING)
+ assertContains(message, "Unexpected JSON token at offset 10: Serializer for subclass 'my.Class' is not found in the polymorphic scope of 'Any' at path: \$.d.a")
+ else
+ assertContains(message, "Serializer for subclass 'my.Class' is not found in the polymorphic scope of 'Any'")
+ })
+ }
+
+ @Test
+ fun testDiscriminatorMissingNoDefaultMessage() = parametrizedTest { mode ->
+ val input = """{"d":{"a":{"value":42}}}"""
+ checkSerializationException({
+ default.decodeFromString<Holder>(input, mode)
+ }, { message ->
+ // Always slow path when discriminator is missing, so no position and path
+ assertContains(message, "Class discriminator was missing and no default serializers were registered in the polymorphic scope of 'Any'")
+ })
+ }
+
+ @Test
+ fun testClassDiscriminatorIsNull() = parametrizedTest { mode ->
+ val input = """{"d":{"a":{"type":null, "value":42}}}"""
+ checkSerializationException({
+ default.decodeFromString<Holder>(input, mode)
+ }, { message ->
+ // Always slow path when discriminator is missing, so no position and path
+ assertContains(message, "Class discriminator was missing and no default serializers were registered in the polymorphic scope of 'Any'")
+ })
+ }
+}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphicOnClassesTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphicOnClassesTest.kt
index ea59f32..77004db 100644
--- a/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphicOnClassesTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphicOnClassesTest.kt
@@ -136,7 +136,7 @@
fun testSerializerLookupForInterface() {
// On JVM and JS IR it can be supported via reflection/runtime hacks
// on Native, unfortunately, only with intrinsics.
- if (isNative()) return
+ if (isNative() || isWasm()) return
val msgSer = serializer<IMessage>()
assertEquals(IMessage::class, (msgSer as AbstractPolymorphicSerializer).baseClass)
}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphismWithAnyTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphismWithAnyTest.kt
index e1d38fd..07b6e31 100644
--- a/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphismWithAnyTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphismWithAnyTest.kt
@@ -5,13 +5,13 @@
package kotlinx.serialization.features
import kotlinx.serialization.*
-import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*
import kotlinx.serialization.modules.plus
import kotlinx.serialization.test.assertStringFormAndRestored
import kotlin.test.*
-class PolymorphismWithAnyTest {
+class PolymorphismWithAnyTest: JsonTestBase() {
@Serializable
data class MyPolyData(val data: Map<String, @Polymorphic Any>)
@@ -28,19 +28,20 @@
val className = className.substringAfterLast('.')
val scopeName = scopeName.substringAfterLast('.')
val expectedText =
- "Class '$className' is not registered for polymorphic serialization in the scope of '$scopeName'"
+ "Serializer for subclass '$className' is not found in the polymorphic scope of '$scopeName'"
assertTrue(exception.message!!.startsWith(expectedText),
"Found $exception, but expected to start with: $expectedText")
}
@Test
- fun testFailWithoutModulesWithCustomClass() {
+ fun testFailWithoutModulesWithCustomClass() = parametrizedTest { mode ->
checkNotRegisteredMessage(
"kotlinx.serialization.IntData", "kotlin.Any",
assertFailsWith<SerializationException>("not registered") {
Json.encodeToString(
MyPolyData.serializer(),
- MyPolyData(mapOf("a" to IntData(42)))
+ MyPolyData(mapOf("a" to IntData(42))),
+ mode
)
}
)
@@ -51,11 +52,11 @@
val json = Json {
serializersModule = SerializersModule { polymorphic(Any::class) { subclass(IntData.serializer()) } }
}
- assertStringFormAndRestored(
+ assertJsonFormAndRestored(
expected = """{"data":{"a":{"type":"kotlinx.serialization.IntData","intV":42}}}""",
- original = MyPolyData(mapOf("a" to IntData(42))),
+ data = MyPolyData(mapOf("a" to IntData(42))),
serializer = MyPolyData.serializer(),
- format = json
+ json = json
)
}
@@ -63,14 +64,15 @@
* This test should fail because PolyDerived registered in the scope of PolyBase, not kotlin.Any
*/
@Test
- fun testFailWithModulesNotInAnyScope() {
+ fun testFailWithModulesNotInAnyScope() = parametrizedTest { mode ->
val json = Json { serializersModule = BaseAndDerivedModule }
checkNotRegisteredMessage(
"kotlinx.serialization.PolyDerived", "kotlin.Any",
assertFailsWith<SerializationException> {
json.encodeToString(
MyPolyData.serializer(),
- MyPolyData(mapOf("a" to PolyDerived("foo")))
+ MyPolyData(mapOf("a" to PolyDerived("foo"))),
+ mode
)
}
)
@@ -86,11 +88,11 @@
@Test
fun testRebindModules() {
val json = Json { serializersModule = baseAndDerivedModuleAtAny }
- assertStringFormAndRestored(
+ assertJsonFormAndRestored(
expected = """{"data":{"a":{"type":"kotlinx.serialization.PolyDerived","id":1,"s":"foo"}}}""",
- original = MyPolyData(mapOf("a" to PolyDerived("foo"))),
+ data = MyPolyData(mapOf("a" to PolyDerived("foo"))),
serializer = MyPolyData.serializer(),
- format = json
+ json = json
)
}
@@ -98,7 +100,7 @@
* This test should fail because PolyDerived registered in the scope of kotlin.Any, not PolyBase
*/
@Test
- fun testFailWithModulesNotInParticularScope() {
+ fun testFailWithModulesNotInParticularScope() = parametrizedTest { mode ->
val json = Json { serializersModule = baseAndDerivedModuleAtAny }
checkNotRegisteredMessage(
"kotlinx.serialization.PolyDerived", "kotlinx.serialization.PolyBase",
@@ -108,7 +110,8 @@
MyPolyDataWithPolyBase(
mapOf("a" to PolyDerived("foo")),
PolyDerived("foo")
- )
+ ),
+ mode
)
}
)
@@ -117,17 +120,30 @@
@Test
fun testBindModules() {
val json = Json { serializersModule = (baseAndDerivedModuleAtAny + BaseAndDerivedModule) }
- assertStringFormAndRestored(
+ assertJsonFormAndRestored(
expected = """{"data":{"a":{"type":"kotlinx.serialization.PolyDerived","id":1,"s":"foo"}},
|"polyBase":{"type":"kotlinx.serialization.PolyDerived","id":1,"s":"foo"}}""".trimMargin().lines().joinToString(
""
),
- original = MyPolyDataWithPolyBase(
+ data = MyPolyDataWithPolyBase(
mapOf("a" to PolyDerived("foo")),
PolyDerived("foo")
),
serializer = MyPolyDataWithPolyBase.serializer(),
- format = json
+ json = json
)
}
+
+ @Test
+ fun testTypeKeyLastInInput() = parametrizedTest { mode ->
+ val json = Json { serializersModule = (baseAndDerivedModuleAtAny + BaseAndDerivedModule) }
+ val input = """{"data":{"a":{"id":1,"s":"foo","type":"kotlinx.serialization.PolyDerived"}},
+ |"polyBase":{"id":1,"s":"foo","type":"kotlinx.serialization.PolyDerived"}}""".trimMargin().lines().joinToString(
+ "")
+ val data = MyPolyDataWithPolyBase(
+ mapOf("a" to PolyDerived("foo")),
+ PolyDerived("foo")
+ )
+ assertEquals(data, json.decodeFromString(MyPolyDataWithPolyBase.serializer(), input, mode))
+ }
}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/BasicTypesSerializationTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/BasicTypesSerializationTest.kt
index dee87fd..4959b7e 100644
--- a/formats/json-tests/commonTest/src/kotlinx/serialization/json/BasicTypesSerializationTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/BasicTypesSerializationTest.kt
@@ -5,24 +5,37 @@
package kotlinx.serialization.json
import kotlinx.serialization.*
+import kotlinx.serialization.test.*
import kotlin.test.*
class BasicTypesSerializationTest : JsonTestBase() {
val goldenValue = """
- {"unit":{},"boolean":true,"byte":10,"short":20,"int":30,"long":40,"float":50.1,"double":60.1,"char":"A","string":"Str0","enum":"POSITIVE","intData":{"intV":70},"unitN":null,"booleanN":null,"byteN":11,"shortN":21,"intN":31,"longN":41,"floatN":51.1,"doubleN":61.1,"charN":"B","stringN":"Str1","enumN":"NEUTRAL","intDataN":null,"listInt":[1,2,3],"listIntN":[4,5,null],"listNInt":[6,7,8],"listNIntN":[null,9,10],"listListEnumN":[["NEGATIVE",null]],"listIntData":[{"intV":1},{"intV":2},{"intV":3}],"listIntDataN":[{"intV":1},null,{"intV":3}],"tree":{"name":"root","left":{"name":"left","left":null,"right":null},"right":{"name":"right","left":{"name":"right.left","left":null,"right":null},"right":{"name":"right.right","left":null,"right":null}}},"mapStringInt":{"one":1,"two":2,"three":3},"mapIntStringN":{"0":null,"1":"first","2":"second"},"arrays":{"arrByte":[1,2,3],"arrInt":[100,200,300],"arrIntN":[null,-1,-2],"arrIntData":[{"intV":1},{"intV":2}]}}
+ {"unit":{},"boolean":true,"byte":10,"short":20,"int":30,"long":40,"float":50.1,"double":60.1,"char":"A","string":"Str0","enum":"POSITIVE","intData":{"intV":70},"unitN":null,"booleanN":null,"byteN":11,"shortN":21,"intN":31,"longN":41,"floatN":51.1,"doubleN":61.1,"charN":"B","stringN":"Str1","enumN":"NEUTRAL","intDataN":null,"listInt":[1,2,3],"listIntN":[4,5,null],"listNInt":[6,7,8],"listNIntN":[null,9,10],"listListEnumN":[["NEGATIVE",null]],"listIntData":[{"intV":1},{"intV":2},{"intV":3}],"listIntDataN":[{"intV":1},null,{"intV":3}],"tree":{"name":"root","left":{"name":"left","left":null,"right":null},"right":{"name":"right","left":{"name":"right.left","left":null,"right":null},"right":{"name":"right.right","left":null,"right":null}}},"mapStringInt":{"one":1,"two":2,"three":3},"mapIntStringN":{"0":null,"1":"first","2":"second"},"arrays":{"arrByte":[1,2,3],"arrInt":[100,200,300],"arrIntN":[null,-1,-2],"arrIntData":[{"intV":1},{"intV":2}]}}
""".trimIndent()
- @Test
- fun testSerialization() = parametrizedTest { jsonTestingMode ->
- val json = default.encodeToString(TypesUmbrella.serializer(), umbrellaInstance)
+ val goldenValue2 = """
+ {"unit":{},"boolean":true,"byte":10,"short":20,"int":30,"long":40,"float":50.5,"double":60.5,"char":"A","string":"Str0","enum":"POSITIVE","intData":{"intV":70},"unitN":null,"booleanN":null,"byteN":11,"shortN":21,"intN":31,"longN":41,"floatN":51.5,"doubleN":61.5,"charN":"B","stringN":"Str1","enumN":"NEUTRAL","intDataN":null,"listInt":[1,2,3],"listIntN":[4,5,null],"listNInt":[6,7,8],"listNIntN":[null,9,10],"listListEnumN":[["NEGATIVE",null]],"listIntData":[{"intV":1},{"intV":2},{"intV":3}],"listIntDataN":[{"intV":1},null,{"intV":3}],"tree":{"name":"root","left":{"name":"left","left":null,"right":null},"right":{"name":"right","left":{"name":"right.left","left":null,"right":null},"right":{"name":"right.right","left":null,"right":null}}},"mapStringInt":{"one":1,"two":2,"three":3},"mapIntStringN":{"0":null,"1":"first","2":"second"},"arrays":{"arrByte":[1,2,3],"arrInt":[100,200,300],"arrIntN":[null,-1,-2],"arrIntData":[{"intV":1},{"intV":2}]}}
+ """.trimIndent()
+
+ private fun testSerializationImpl(typesUmbrella: TypesUmbrella, goldenValue: String) = parametrizedTest { jsonTestingMode ->
+ val json = default.encodeToString(TypesUmbrella.serializer(), typesUmbrella)
assertEquals(goldenValue, json)
val instance = default.decodeFromString(TypesUmbrella.serializer(), json, jsonTestingMode)
- assertEquals(umbrellaInstance, instance)
- assertNotSame(umbrellaInstance, instance)
+ assertEquals(typesUmbrella, instance)
+ assertNotSame(typesUmbrella, instance)
}
@Test
+ fun testSerialization() {
+ if (isWasm()) return //https://youtrack.jetbrains.com/issue/KT-59118/WASM-floating-point-toString-inconsistencies
+ testSerializationImpl(umbrellaInstance, goldenValue)
+ }
+
+ @Test
+ fun testSerialization2() = testSerializationImpl(umbrellaInstance2, goldenValue2)
+
+ @Test
fun testTopLevelPrimitive() = parametrizedTest { jsonTestingMode ->
testPrimitive(Unit, "{}", jsonTestingMode)
testPrimitive(false, "false", jsonTestingMode)
@@ -30,8 +43,8 @@
testPrimitive(2.toShort(), "2", jsonTestingMode)
testPrimitive(3, "3", jsonTestingMode)
testPrimitive(4L, "4", jsonTestingMode)
- testPrimitive(5.1f, "5.1", jsonTestingMode)
- testPrimitive(6.1, "6.1", jsonTestingMode)
+ testPrimitive(2.5f, "2.5", jsonTestingMode)
+ testPrimitive(3.5, "3.5", jsonTestingMode)
testPrimitive('c', "\"c\"", jsonTestingMode)
testPrimitive("string", "\"string\"", jsonTestingMode)
}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonElementDecodingTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonElementDecodingTest.kt
index d0b105a..3cdfa08 100644
--- a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonElementDecodingTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonElementDecodingTest.kt
@@ -1,6 +1,8 @@
package kotlinx.serialization.json
-import kotlinx.serialization.Serializable
+import kotlinx.serialization.*
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
import kotlin.test.*
class JsonElementDecodingTest : JsonTestBase() {
@@ -51,4 +53,58 @@
json = json.replace("%", "0")
Json.parseToJsonElement(json)
}
+
+ private open class NullAsElementSerializer<T : Any>(private val serializer: KSerializer<T>, val nullElement: T) : KSerializer<T?> {
+ final override val descriptor: SerialDescriptor = serializer.descriptor.nullable
+
+ final override fun serialize(encoder: Encoder, value: T?) {
+ serializer.serialize(encoder, value ?: nullElement)
+ }
+
+ final override fun deserialize(decoder: Decoder): T = serializer.deserialize(decoder)
+ }
+
+ private object NullAsJsonNullJsonElementSerializer : NullAsElementSerializer<JsonElement>(JsonElement.serializer(), JsonNull)
+ private object NullAsJsonNullJsonPrimitiveSerializer : NullAsElementSerializer<JsonPrimitive>(JsonPrimitive.serializer(), JsonNull)
+ private object NullAsJsonNullJsonNullSerializer : NullAsElementSerializer<JsonNull>(JsonNull.serializer(), JsonNull)
+ private val noExplicitNullsOrDefaultsJson = Json {
+ explicitNulls = false
+ encodeDefaults = false
+ }
+
+ @Test
+ fun testNullableJsonElementDecoding() {
+ @Serializable
+ data class Wrapper(
+ @Serializable(NullAsJsonNullJsonElementSerializer::class)
+ val value: JsonElement? = null,
+ )
+
+ assertJsonFormAndRestored(Wrapper.serializer(), Wrapper(value = JsonNull), """{"value":null}""", noExplicitNullsOrDefaultsJson)
+ assertJsonFormAndRestored(Wrapper.serializer(), Wrapper(value = null), """{}""", noExplicitNullsOrDefaultsJson)
+ }
+
+ @Test
+ fun testNullableJsonPrimitiveDecoding() {
+ @Serializable
+ data class Wrapper(
+ @Serializable(NullAsJsonNullJsonPrimitiveSerializer::class)
+ val value: JsonPrimitive? = null,
+ )
+
+ assertJsonFormAndRestored(Wrapper.serializer(), Wrapper(value = JsonNull), """{"value":null}""", noExplicitNullsOrDefaultsJson)
+ assertJsonFormAndRestored(Wrapper.serializer(), Wrapper(value = null), """{}""", noExplicitNullsOrDefaultsJson)
+ }
+
+ @Test
+ fun testNullableJsonNullDecoding() {
+ @Serializable
+ data class Wrapper(
+ @Serializable(NullAsJsonNullJsonNullSerializer::class)
+ val value: JsonNull? = null,
+ )
+
+ assertJsonFormAndRestored(Wrapper.serializer(), Wrapper(value = JsonNull), """{"value":null}""", noExplicitNullsOrDefaultsJson)
+ assertJsonFormAndRestored(Wrapper.serializer(), Wrapper(value = null), """{}""", noExplicitNullsOrDefaultsJson)
+ }
}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonErrorMessagesTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonErrorMessagesTest.kt
index 8c16ac0..08d1eef 100644
--- a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonErrorMessagesTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonErrorMessagesTest.kt
@@ -6,6 +6,7 @@
package kotlinx.serialization.json
import kotlinx.serialization.*
+import kotlinx.serialization.test.*
import kotlin.test.*
@@ -155,11 +156,4 @@
})
}
-
- private fun checkSerializationException(action: () -> Unit, assertions: SerializationException.(String) -> Unit) {
- val e = assertFailsWith(SerializationException::class, action)
- assertNotNull(e.message)
- e.assertions(e.message!!)
- }
-
}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt
index 61f31de..560e51f 100644
--- a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt
@@ -5,6 +5,7 @@
package kotlinx.serialization.json
import kotlinx.serialization.*
+import kotlinx.serialization.builtins.*
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
@@ -42,6 +43,9 @@
private data class WithMap(val map: Map<Long, Long>)
@Serializable
+ private data class WithBooleanMap(val map: Map<Boolean, Boolean>)
+
+ @Serializable
private data class WithValueKeyMap(val map: Map<PrimitiveCarrier, Long>)
@Serializable
@@ -60,16 +64,45 @@
private data class WithContextualKey(val map: Map<@Contextual ContextualValue, Long>)
@Test
- fun testMapKeysShouldBeStrings() = parametrizedTest(default) {
+ fun testMapKeysSupportNumbers() = parametrizedTest {
assertStringFormAndRestored(
"""{"map":{"10":10,"20":20}}""",
WithMap(mapOf(10L to 10L, 20L to 20L)),
WithMap.serializer(),
- this
+ default
)
}
@Test
+ fun testMapKeysSupportBooleans() = parametrizedTest {
+ assertStringFormAndRestored(
+ """{"map":{"true":false,"false":true}}""",
+ WithBooleanMap(mapOf(true to false, false to true)),
+ WithBooleanMap.serializer(),
+ default
+ )
+ }
+
+ // As a result of quoting ignorance when parsing primitives, it is possible to parse unquoted maps if Kotlin keys are non-string primitives.
+ // This is not spec-compliant, but I do not see any problems with it.
+ @Test
+ fun testMapDeserializesUnquotedKeys() = parametrizedTest {
+ assertEquals(WithMap(mapOf(10L to 10L, 20L to 20L)), default.decodeFromString("""{"map":{10:10,20:20}}"""))
+ assertEquals(
+ WithBooleanMap(mapOf(true to false, false to true)),
+ default.decodeFromString("""{"map":{true:false,false:true}}""")
+ )
+ assertFailsWithSerial("JsonDecodingException") {
+ default.decodeFromString(
+ MapSerializer(
+ String.serializer(),
+ Boolean.serializer()
+ ),"""{"map":{true:false,false:true}}"""
+ )
+ }
+ }
+
+ @Test
fun testStructuredMapKeysShouldBeProhibitedByDefault() = parametrizedTest { streaming ->
verifyProhibition(WithComplexKey(mapOf(IntData(42) to "42")), streaming)
verifyProhibition(WithComplexValueKey(mapOf(ComplexCarrier(IntData(42)) to "42")), streaming)
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonParserTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonParserTest.kt
index 7f9044f..94f7052 100644
--- a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonParserTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonParserTest.kt
@@ -83,7 +83,7 @@
}
private fun testTrailingComma(content: String) {
- assertFailsWithSerialMessage("JsonDecodingException", "Unexpected trailing") { Json.parseToJsonElement(content) }
+ assertFailsWithSerialMessage("JsonDecodingException", "Trailing comma before the end of JSON object") { Json.parseToJsonElement(content) }
}
@Test
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTestBase.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTestBase.kt
index ebcc3d0..6f3b132 100644
--- a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTestBase.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTestBase.kt
@@ -49,7 +49,7 @@
encodeViaStream(serializer, value)
}
JsonTestingMode.TREE -> {
- val tree = writeJson(value, serializer)
+ val tree = writeJson(this, value, serializer)
encodeToString(tree)
}
JsonTestingMode.OKIO_STREAMS -> {
@@ -77,8 +77,8 @@
decodeViaStream(deserializer, source)
}
JsonTestingMode.TREE -> {
- val tree = decodeStringToJsonTree(deserializer, source)
- readJson(tree, deserializer)
+ val tree = decodeStringToJsonTree(this, deserializer, source)
+ readJson(this, tree, deserializer)
}
JsonTestingMode.OKIO_STREAMS -> {
val buffer = Buffer()
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/LenientTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/LenientTest.kt
index 41d3c2c..d24c7d7 100644
--- a/formats/json-tests/commonTest/src/kotlinx/serialization/json/LenientTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/LenientTest.kt
@@ -37,7 +37,7 @@
@Test
fun testQuotedBoolean() = parametrizedTest {
val json = """{"i":1, "l":2, "b":"true", "s":"string"}"""
- assertFailsWithSerial("JsonDecodingException") { default.decodeFromString(Holder.serializer(), json, it) }
+ assertEquals(value, default.decodeFromString(Holder.serializer(), json, it))
assertEquals(value, lenient.decodeFromString(Holder.serializer(), json, it))
}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/TrailingCommaTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/TrailingCommaTest.kt
new file mode 100644
index 0000000..0916de5
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/TrailingCommaTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.json
+
+import kotlinx.serialization.*
+import kotlinx.serialization.test.*
+import kotlin.test.*
+
+class TrailingCommaTest : JsonTestBase() {
+ val tj = Json { allowTrailingComma = true }
+
+ @Serializable
+ data class Optional(val data: String = "")
+
+ @Serializable
+ data class MultipleFields(val a: String, val b: String, val c: String)
+
+ private val multipleFields = MultipleFields("1", "2", "3")
+
+ @Serializable
+ data class WithMap(val m: Map<String, String>)
+
+ private val withMap = WithMap(mapOf("a" to "1", "b" to "2", "c" to "3"))
+
+ @Serializable
+ data class WithList(val l: List<Int>)
+
+ private val withList = WithList(listOf(1, 2, 3))
+
+ @Test
+ fun basic() = parametrizedTest { mode ->
+ val sd = """{"data":"str",}"""
+ assertEquals(Optional("str"), tj.decodeFromString<Optional>(sd, mode))
+ }
+
+ @Test
+ fun trailingCommaNotAllowedByDefaultForObjects() = parametrizedTest { mode ->
+ val sd = """{"data":"str",}"""
+ checkSerializationException({
+ default.decodeFromString<Optional>(sd, mode)
+ }, { message ->
+ assertContains(
+ message,
+ """Unexpected JSON token at offset 13: Trailing comma before the end of JSON object"""
+ )
+ })
+ }
+
+ @Test
+ fun trailingCommaNotAllowedByDefaultForLists() = parametrizedTest { mode ->
+ val sd = """{"l":[1,]}"""
+ checkSerializationException({
+ default.decodeFromString<WithList>(sd, mode)
+ }, { message ->
+ assertContains(
+ message,
+ """Unexpected JSON token at offset 7: Trailing comma before the end of JSON array"""
+ )
+ })
+ }
+
+ @Test
+ fun trailingCommaNotAllowedByDefaultForMaps() = parametrizedTest { mode ->
+ val sd = """{"m":{"a": "b",}}"""
+ checkSerializationException({
+ default.decodeFromString<WithMap>(sd, mode)
+ }, { message ->
+ assertContains(
+ message,
+ """Unexpected JSON token at offset 14: Trailing comma before the end of JSON object"""
+ )
+ })
+ }
+
+ @Test
+ fun emptyObjectNotAllowed() = parametrizedTest { mode ->
+ assertFailsWithMessage<SerializationException>("Unexpected leading comma") {
+ tj.decodeFromString<Optional>("""{,}""", mode)
+ }
+ }
+
+ @Test
+ fun emptyListNotAllowed() = parametrizedTest { mode ->
+ assertFailsWithMessage<SerializationException>("Unexpected leading comma") {
+ tj.decodeFromString<WithList>("""{"l":[,]}""", mode)
+ }
+ }
+
+ @Test
+ fun emptyMapNotAllowed() = parametrizedTest { mode ->
+ assertFailsWithMessage<SerializationException>("Unexpected leading comma") {
+ tj.decodeFromString<WithMap>("""{"m":{,}}""", mode)
+ }
+ }
+
+ @Test
+ fun testMultipleFields() = parametrizedTest { mode ->
+ val input = """{"a":"1","b":"2","c":"3", }"""
+ assertEquals(multipleFields, tj.decodeFromString(input, mode))
+ }
+
+ @Test
+ fun testWithMap() = parametrizedTest { mode ->
+ val input = """{"m":{"a":"1","b":"2","c":"3", }}"""
+
+ assertEquals(withMap, tj.decodeFromString(input, mode))
+ }
+
+ @Test
+ fun testWithList() = parametrizedTest { mode ->
+ val input = """{"l":[1, 2, 3, ]}"""
+ assertEquals(withList, tj.decodeFromString(input, mode))
+ }
+
+ @Serializable
+ data class Mixed(val mf: MultipleFields, val wm: WithMap, val wl: WithList)
+
+ @Test
+ fun testMixed() = parametrizedTest { mode ->
+ //language=JSON5
+ val input = """{"mf":{"a":"1","b":"2","c":"3",},
+ "wm":{"m":{"a":"1","b":"2","c":"3",},},
+ "wl":{"l":[1, 2, 3,],},}"""
+ assertEquals(Mixed(multipleFields, withMap, withList), tj.decodeFromString(input, mode))
+ }
+}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt
index 92cf087..8c3633b 100644
--- a/formats/json-tests/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt
@@ -5,7 +5,7 @@
package kotlinx.serialization.test
enum class Platform {
- JVM, JS, NATIVE
+ JVM, JS, NATIVE, WASM
}
public expect val currentPlatform: Platform
@@ -13,3 +13,4 @@
public fun isJs(): Boolean = currentPlatform == Platform.JS
public fun isJvm(): Boolean = currentPlatform == Platform.JVM
public fun isNative(): Boolean = currentPlatform == Platform.NATIVE
+public fun isWasm(): Boolean = currentPlatform == Platform.WASM
\ No newline at end of file
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/test/TestingFramework.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/test/TestingFramework.kt
index e941f04..3ec0714 100644
--- a/formats/json-tests/commonTest/src/kotlinx/serialization/test/TestingFramework.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/test/TestingFramework.kt
@@ -92,3 +92,9 @@
"expected:<$message> but was:<${exception.message}>"
)
}
+
+inline fun checkSerializationException(action: () -> Unit, assertions: SerializationException.(String) -> Unit) {
+ val e = assertFailsWith(SerializationException::class, action)
+ assertNotNull(e.message)
+ e.assertions(e.message!!)
+}
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/JavaCollectionsTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/JavaCollectionsTest.kt
index a0ff55a..1f4958a 100644
--- a/formats/json-tests/jvmTest/src/kotlinx/serialization/JavaCollectionsTest.kt
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/JavaCollectionsTest.kt
@@ -17,6 +17,7 @@
package kotlinx.serialization
import kotlinx.serialization.json.Json
+import kotlinx.serialization.test.typeTokenOf
import org.junit.Test
import java.util.HashMap
import java.util.HashSet
@@ -24,7 +25,8 @@
import kotlin.collections.Map
import kotlin.collections.hashMapOf
import kotlin.collections.hashSetOf
-import kotlin.test.assertEquals
+import kotlin.reflect.*
+import kotlin.test.*
class JavaCollectionsTest {
@@ -38,7 +40,7 @@
)
@Test
- fun test() {
+ fun testJavaCollectionsInsideClass() {
val original = HasHashMap("42", hashMapOf(1 to "1", 2 to "2"), hashSetOf(11), LinkedHashMap(), null)
val serializer = HasHashMap.serializer()
val string = Json.encodeToString(serializer = serializer, value = original)
@@ -49,4 +51,62 @@
val restored = Json.decodeFromString(deserializer = serializer, string = string)
assertEquals(expected = original, actual = restored)
}
+
+ @Test
+ fun testTopLevelMaps() {
+ // Returning null here is a deliberate choice: map constructor functions may return different specialized
+ // implementations (e.g., kotlin.collections.EmptyMap or java.util.Collections.SingletonMap)
+ // that may or may not be generic. Since we generally cannot return a generic serializer using Java class only,
+ // all attempts to get map serializer using only .javaClass should return null.
+ assertNull(serializerOrNull(emptyMap<String, String>().javaClass))
+ assertNull(serializerOrNull(mapOf<String, String>("a" to "b").javaClass))
+ assertNull(serializerOrNull(mapOf<String, String>("a" to "b", "b" to "c").javaClass))
+ // Correct ways of retrieving map serializer:
+ assertContains(
+ serializer(typeTokenOf<Map<String, String>>()).descriptor.serialName,
+ "kotlin.collections.LinkedHashMap"
+ )
+ assertContains(
+ serializer(typeTokenOf<java.util.LinkedHashMap<String, String>>()).descriptor.serialName,
+ "kotlin.collections.LinkedHashMap"
+ )
+ assertContains(
+ serializer(typeOf<LinkedHashMap<String, String>>()).descriptor.serialName,
+ "kotlin.collections.LinkedHashMap"
+ )
+ }
+
+ @Test
+ fun testTopLevelSetsAndLists() {
+ // Same reasoning as for maps
+ assertNull(serializerOrNull(emptyList<String>().javaClass))
+ assertNull(serializerOrNull(listOf<String>("a").javaClass))
+ assertNull(serializerOrNull(listOf<String>("a", "b").javaClass))
+ assertNull(serializerOrNull(emptySet<String>().javaClass))
+ assertNull(serializerOrNull(setOf<String>("a").javaClass))
+ assertNull(serializerOrNull(setOf<String>("a", "b").javaClass))
+ assertContains(
+ serializer(typeTokenOf<Set<String>>()).descriptor.serialName,
+ "kotlin.collections.LinkedHashSet"
+ )
+ assertContains(
+ serializer(typeTokenOf<List<String>>()).descriptor.serialName,
+ "kotlin.collections.ArrayList"
+ )
+ assertContains(
+ serializer(typeTokenOf<java.util.LinkedHashSet<String>>()).descriptor.serialName,
+ "kotlin.collections.LinkedHashSet"
+ )
+ assertContains(
+ serializer(typeTokenOf<java.util.ArrayList<String>>()).descriptor.serialName,
+ "kotlin.collections.ArrayList"
+ )
+ }
+
+ @Test
+ fun testAnonymousObject() {
+ val obj: Any = object {}
+ assertNull(serializerOrNull(obj.javaClass))
+ }
}
+
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/features/SerializerByTypeTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/SerializerByTypeTest.kt
index 3bc4528..a600b9d 100644
--- a/formats/json-tests/jvmTest/src/kotlinx/serialization/features/SerializerByTypeTest.kt
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/SerializerByTypeTest.kt
@@ -209,15 +209,6 @@
assertEquals(expected, json.encodeToString(serial2 as KSerializer<T>, value))
}
- @PublishedApi
- internal open class TypeBase<T>
-
- public inline fun <reified T> typeTokenOf(): Type {
- val base = object : TypeBase<T>() {}
- val superType = base::class.java.genericSuperclass!!
- return (superType as ParameterizedType).actualTypeArguments.first()!!
- }
-
class IntBox(val i: Int)
object CustomIntSerializer : KSerializer<IntBox> {
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/test/TypeToken.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/test/TypeToken.kt
new file mode 100644
index 0000000..2b04274
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/test/TypeToken.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.test
+
+import java.lang.reflect.*
+
+
+@PublishedApi
+internal open class TypeBase<T>
+
+public inline fun <reified T> typeTokenOf(): Type {
+ val base = object : TypeBase<T>() {}
+ val superType = base::class.java.genericSuperclass!!
+ return (superType as ParameterizedType).actualTypeArguments.first()!!
+}
diff --git a/formats/json-tests/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt b/formats/json-tests/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt
new file mode 100644
index 0000000..fd359b7
--- /dev/null
+++ b/formats/json-tests/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt
@@ -0,0 +1,7 @@
+/*
+ * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.test
+
+public actual val currentPlatform: Platform = Platform.WASM
\ No newline at end of file
diff --git a/formats/json-tests/wasmTest/src/kotlinx/serialization/test/JsonHelpers.kt b/formats/json-tests/wasmTest/src/kotlinx/serialization/test/JsonHelpers.kt
new file mode 100644
index 0000000..6102e58
--- /dev/null
+++ b/formats/json-tests/wasmTest/src/kotlinx/serialization/test/JsonHelpers.kt
@@ -0,0 +1,19 @@
+package kotlinx.serialization.test
+
+import kotlinx.serialization.DeserializationStrategy
+import kotlinx.serialization.SerializationStrategy
+import kotlinx.serialization.json.Json
+
+actual fun <T> Json.encodeViaStream(
+ serializer: SerializationStrategy<T>,
+ value: T
+): String {
+ TODO("supported on JVM only")
+}
+
+actual fun <T> Json.decodeViaStream(
+ serializer: DeserializationStrategy<T>,
+ input: String
+): T {
+ TODO("supported on JVM only")
+}
\ No newline at end of file
diff --git a/formats/json/api/kotlinx-serialization-json.api b/formats/json/api/kotlinx-serialization-json.api
index e6a2328..649cce0 100644
--- a/formats/json/api/kotlinx-serialization-json.api
+++ b/formats/json/api/kotlinx-serialization-json.api
@@ -87,6 +87,7 @@
public final class kotlinx/serialization/json/JsonBuilder {
public final fun getAllowSpecialFloatingPointValues ()Z
public final fun getAllowStructuredMapKeys ()Z
+ public final fun getAllowTrailingComma ()Z
public final fun getClassDiscriminator ()Ljava/lang/String;
public final fun getCoerceInputValues ()Z
public final fun getDecodeEnumsCaseInsensitive ()Z
@@ -102,6 +103,7 @@
public final fun isLenient ()Z
public final fun setAllowSpecialFloatingPointValues (Z)V
public final fun setAllowStructuredMapKeys (Z)V
+ public final fun setAllowTrailingComma (Z)V
public final fun setClassDiscriminator (Ljava/lang/String;)V
public final fun setCoerceInputValues (Z)V
public final fun setDecodeEnumsCaseInsensitive (Z)V
@@ -130,6 +132,7 @@
public fun <init> ()V
public final fun getAllowSpecialFloatingPointValues ()Z
public final fun getAllowStructuredMapKeys ()Z
+ public final fun getAllowTrailingComma ()Z
public final fun getClassDiscriminator ()Ljava/lang/String;
public final fun getCoerceInputValues ()Z
public final fun getDecodeEnumsCaseInsensitive ()Z
@@ -383,14 +386,11 @@
public static final fun encodeToStream (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Ljava/io/OutputStream;)V
}
-public final class kotlinx/serialization/json/internal/JsonStreamsKt {
- public static final fun decodeByReader (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/DeserializationStrategy;Lkotlinx/serialization/json/internal/SerialReader;)Ljava/lang/Object;
- public static final fun decodeToSequenceByReader (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/json/internal/SerialReader;Lkotlinx/serialization/DeserializationStrategy;Lkotlinx/serialization/json/DecodeSequenceMode;)Lkotlin/sequences/Sequence;
- public static synthetic fun decodeToSequenceByReader$default (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/json/internal/SerialReader;Lkotlinx/serialization/DeserializationStrategy;Lkotlinx/serialization/json/DecodeSequenceMode;ILjava/lang/Object;)Lkotlin/sequences/Sequence;
- public static final fun encodeByWriter (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/json/internal/JsonWriter;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V
+public abstract interface class kotlinx/serialization/json/internal/InternalJsonReader {
+ public abstract fun read ([CII)I
}
-public abstract interface class kotlinx/serialization/json/internal/JsonWriter {
+public abstract interface class kotlinx/serialization/json/internal/InternalJsonWriter {
public abstract fun release ()V
public abstract fun write (Ljava/lang/String;)V
public abstract fun writeChar (C)V
@@ -398,18 +398,17 @@
public abstract fun writeQuoted (Ljava/lang/String;)V
}
-public abstract interface class kotlinx/serialization/json/internal/SerialReader {
- public abstract fun read ([CII)I
+public final class kotlinx/serialization/json/internal/JsonStreamsKt {
+ public static final fun decodeByReader (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/DeserializationStrategy;Lkotlinx/serialization/json/internal/InternalJsonReader;)Ljava/lang/Object;
+ public static final fun decodeToSequenceByReader (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/json/internal/InternalJsonReader;Lkotlinx/serialization/DeserializationStrategy;Lkotlinx/serialization/json/DecodeSequenceMode;)Lkotlin/sequences/Sequence;
+ public static synthetic fun decodeToSequenceByReader$default (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/json/internal/InternalJsonReader;Lkotlinx/serialization/DeserializationStrategy;Lkotlinx/serialization/json/DecodeSequenceMode;ILjava/lang/Object;)Lkotlin/sequences/Sequence;
+ public static final fun encodeByWriter (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/json/internal/InternalJsonWriter;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V
}
public final class kotlinx/serialization/json/internal/StreamingJsonDecoderKt {
public static final fun decodeStringToJsonTree (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/DeserializationStrategy;Ljava/lang/String;)Lkotlinx/serialization/json/JsonElement;
}
-public final class kotlinx/serialization/json/internal/StringOpsKt {
- public static final fun getESCAPE_STRINGS ()[Ljava/lang/String;
-}
-
public final class kotlinx/serialization/json/internal/TreeJsonDecoderKt {
public static final fun readJson (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/json/JsonElement;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object;
}
diff --git a/formats/json/build.gradle b/formats/json/build.gradle
index 8f2b5af..a71366f 100644
--- a/formats/json/build.gradle
+++ b/formats/json/build.gradle
@@ -1,3 +1,5 @@
+import static KotlinVersion.isKotlinVersionAtLeast
+
/*
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@@ -19,14 +21,35 @@
}
kotlin {
-
sourceSets {
+ configureEach {
+ languageSettings {
+ optIn("kotlinx.serialization.internal.CoreFriendModuleApi")
+ optIn("kotlinx.serialization.json.internal.JsonFriendModuleApi")
+ }
+ }
commonMain {
dependencies {
api project(":kotlinx-serialization-core")
}
}
+ jsWasmMain {
+ dependsOn(sourceSets.commonMain)
+ }
+ jsMain {
+ dependsOn(sourceSets.jsWasmMain)
+ }
+ wasmJsMain {
+ dependsOn(sourceSets.jsWasmMain)
+ }
}
}
Java9Modularity.configureJava9ModuleInfo(project)
+
+// This task should be disabled because of no need to build and publish intermediate JsWasm sourceset
+tasks.whenTaskAdded { task ->
+ if (task.name == 'compileJsWasmMainKotlinMetadata') {
+ task.enabled = false
+ }
+}
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt b/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt
index 40dcc23..26c376e 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt
@@ -78,7 +78,7 @@
public final override fun <T> encodeToString(serializer: SerializationStrategy<T>, value: T): String {
val result = JsonToStringWriter()
try {
- encodeByWriter(result, serializer, value)
+ encodeByWriter(this@Json, result, serializer, value)
return result.toString()
} finally {
result.release()
@@ -114,7 +114,7 @@
* @throws [SerializationException] if the given value cannot be serialized to JSON
*/
public fun <T> encodeToJsonElement(serializer: SerializationStrategy<T>, value: T): JsonElement {
- return writeJson(value, serializer)
+ return writeJson(this@Json, value, serializer)
}
/**
@@ -124,7 +124,7 @@
* @throws [IllegalArgumentException] if the decoded input cannot be represented as a valid instance of type [T]
*/
public fun <T> decodeFromJsonElement(deserializer: DeserializationStrategy<T>, element: JsonElement): T {
- return readJson(element, deserializer)
+ return readJson(this@Json, element, deserializer)
}
/**
@@ -365,6 +365,16 @@
public var decodeEnumsCaseInsensitive: Boolean = json.configuration.decodeEnumsCaseInsensitive
/**
+ * Allows parser to accept trailing (ending) commas in JSON objects and arrays,
+ * making inputs like `[1, 2, 3,]` valid.
+ *
+ * Does not affect encoding.
+ * `false` by default.
+ */
+ @ExperimentalSerializationApi
+ public var allowTrailingComma: Boolean = json.configuration.allowTrailingComma
+
+ /**
* Module with contextual and polymorphic serializers to be used in the resulting [Json] instance.
*
* @see SerializersModule
@@ -396,7 +406,7 @@
allowStructuredMapKeys, prettyPrint, explicitNulls, prettyPrintIndent,
coerceInputValues, useArrayPolymorphism,
classDiscriminator, allowSpecialFloatingPointValues, useAlternativeNames,
- namingStrategy, decodeEnumsCaseInsensitive
+ namingStrategy, decodeEnumsCaseInsensitive, allowTrailingComma
)
}
}
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt b/formats/json/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt
index ea653a6..053f4cd 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt
@@ -32,7 +32,9 @@
@ExperimentalSerializationApi
public val namingStrategy: JsonNamingStrategy? = null,
@ExperimentalSerializationApi
- public val decodeEnumsCaseInsensitive: Boolean = false
+ public val decodeEnumsCaseInsensitive: Boolean = false,
+ @ExperimentalSerializationApi
+ public val allowTrailingComma: Boolean = false,
) {
/** @suppress Dokka **/
@@ -42,6 +44,6 @@
"allowStructuredMapKeys=$allowStructuredMapKeys, prettyPrint=$prettyPrint, explicitNulls=$explicitNulls, " +
"prettyPrintIndent='$prettyPrintIndent', coerceInputValues=$coerceInputValues, useArrayPolymorphism=$useArrayPolymorphism, " +
"classDiscriminator='$classDiscriminator', allowSpecialFloatingPointValues=$allowSpecialFloatingPointValues, useAlternativeNames=$useAlternativeNames, " +
- "namingStrategy=$namingStrategy, decodeEnumsCaseInsensitive=$decodeEnumsCaseInsensitive)"
+ "namingStrategy=$namingStrategy, decodeEnumsCaseInsensitive=$decodeEnumsCaseInsensitive, allowTrailingComma=$allowTrailingComma)"
}
}
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/JsonTransformingSerializer.kt b/formats/json/commonMain/src/kotlinx/serialization/json/JsonTransformingSerializer.kt
index 8d1485d..22d87a7 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/JsonTransformingSerializer.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/JsonTransformingSerializer.kt
@@ -67,7 +67,7 @@
final override fun serialize(encoder: Encoder, value: T) {
val output = encoder.asJsonEncoder()
- var element = output.json.writeJson(value, tSerializer)
+ var element = writeJson(output.json, value, tSerializer)
element = transformSerialize(element)
output.encodeJsonElement(element)
}
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/Composers.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/Composers.kt
index d584155..c1ed8cc 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/Composers.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/Composers.kt
@@ -9,11 +9,11 @@
import kotlinx.serialization.json.*
import kotlin.jvm.*
-internal fun Composer(sb: JsonWriter, json: Json): Composer =
+internal fun Composer(sb: InternalJsonWriter, json: Json): Composer =
if (json.configuration.prettyPrint) ComposerWithPrettyPrint(sb, json) else Composer(sb)
@OptIn(ExperimentalSerializationApi::class)
-internal open class Composer(@JvmField internal val writer: JsonWriter) {
+internal open class Composer(@JvmField internal val writer: InternalJsonWriter) {
var writingFirst = true
protected set
@@ -42,7 +42,7 @@
}
@SuppressAnimalSniffer // Long(Integer).toUnsignedString(long)
-internal class ComposerForUnsignedNumbers(writer: JsonWriter, private val forceQuoting: Boolean) : Composer(writer) {
+internal class ComposerForUnsignedNumbers(writer: InternalJsonWriter, private val forceQuoting: Boolean) : Composer(writer) {
override fun print(v: Int) {
if (forceQuoting) printQuoted(v.toUInt().toString()) else print(v.toUInt().toString())
}
@@ -61,14 +61,14 @@
}
@SuppressAnimalSniffer
-internal class ComposerForUnquotedLiterals(writer: JsonWriter, private val forceQuoting: Boolean) : Composer(writer) {
+internal class ComposerForUnquotedLiterals(writer: InternalJsonWriter, private val forceQuoting: Boolean) : Composer(writer) {
override fun printQuoted(value: String) {
if (forceQuoting) super.printQuoted(value) else super.print(value)
}
}
internal class ComposerWithPrettyPrint(
- writer: JsonWriter,
+ writer: InternalJsonWriter,
private val json: Json
) : Composer(writer) {
private var level = 0
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonElementMarker.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonElementMarker.kt
index 2535739..85a4624 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonElementMarker.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonElementMarker.kt
@@ -2,7 +2,6 @@
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
package kotlinx.serialization.json.internal
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonExceptions.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonExceptions.kt
index ad5011a..c6098dd 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonExceptions.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonExceptions.kt
@@ -46,6 +46,13 @@
hint = specialFlowingValuesHint)
}
+internal fun AbstractJsonLexer.invalidTrailingComma(entity: String = "object"): Nothing {
+ fail("Trailing comma before the end of JSON $entity",
+ position = currentPosition - 1,
+ hint = "Trailing commas are non-complaint JSON and not allowed by default. Use 'allowTrailingCommas = true' in 'Json {}' builder to support them."
+ )
+}
+
@OptIn(ExperimentalSerializationApi::class)
internal fun InvalidKeyKindException(keyDescriptor: SerialDescriptor) = JsonEncodingException(
"Value of type '${keyDescriptor.serialName}' can't be used in JSON as a key in the map. " +
@@ -75,7 +82,7 @@
"Current input: ${input.minify()}"
)
-private fun CharSequence.minify(offset: Int = -1): CharSequence {
+internal fun CharSequence.minify(offset: Int = -1): CharSequence {
if (length < 200) return this
if (offset == -1) {
val start = this.length - 60
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonStreams.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonStreams.kt
index 74dbe45..05f025c 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonStreams.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonStreams.kt
@@ -4,38 +4,42 @@
import kotlinx.serialization.json.DecodeSequenceMode
import kotlinx.serialization.json.Json
-@PublishedApi
-internal interface JsonWriter {
- fun writeLong(value: Long)
- fun writeChar(char: Char)
- fun write(text: String)
- fun writeQuoted(text: String)
- fun release()
+@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
+internal annotation class JsonFriendModuleApi
+
+@JsonFriendModuleApi
+public interface InternalJsonWriter {
+ public fun writeLong(value: Long)
+ public fun writeChar(char: Char)
+ public fun write(text: String)
+ public fun writeQuoted(text: String)
+ public fun release()
}
-@PublishedApi
-internal interface SerialReader {
- fun read(buffer: CharArray, bufferOffset: Int, count: Int): Int
+@JsonFriendModuleApi
+public interface InternalJsonReader {
+ public fun read(buffer: CharArray, bufferOffset: Int, count: Int): Int
}
-@PublishedApi
-internal fun <T> Json.encodeByWriter(writer: JsonWriter, serializer: SerializationStrategy<T>, value: T) {
+@JsonFriendModuleApi
+public fun <T> encodeByWriter(json: Json, writer: InternalJsonWriter, serializer: SerializationStrategy<T>, value: T) {
val encoder = StreamingJsonEncoder(
- writer, this,
+ writer, json,
WriteMode.OBJ,
- arrayOfNulls(WriteMode.values().size)
+ arrayOfNulls(WriteMode.entries.size)
)
encoder.encodeSerializableValue(serializer, value)
}
-@PublishedApi
-internal fun <T> Json.decodeByReader(
+@JsonFriendModuleApi
+public fun <T> decodeByReader(
+ json: Json,
deserializer: DeserializationStrategy<T>,
- reader: SerialReader
+ reader: InternalJsonReader
): T {
val lexer = ReaderJsonLexer(reader)
try {
- val input = StreamingJsonDecoder(this, WriteMode.OBJ, lexer, deserializer.descriptor, null)
+ val input = StreamingJsonDecoder(json, WriteMode.OBJ, lexer, deserializer.descriptor, null)
val result = input.decodeSerializableValue(deserializer)
lexer.expectEof()
return result
@@ -44,21 +48,23 @@
}
}
-@PublishedApi
+@JsonFriendModuleApi
@ExperimentalSerializationApi
-internal fun <T> Json.decodeToSequenceByReader(
- reader: SerialReader,
+public fun <T> decodeToSequenceByReader(
+ json: Json,
+ reader: InternalJsonReader,
deserializer: DeserializationStrategy<T>,
format: DecodeSequenceMode = DecodeSequenceMode.AUTO_DETECT
): Sequence<T> {
val lexer = ReaderJsonLexer(reader, CharArray(BATCH_SIZE)) // Unpooled buffer due to lazy nature of sequence
- val iter = JsonIterator(format, this, lexer, deserializer)
+ val iter = JsonIterator(format, json, lexer, deserializer)
return Sequence { iter }.constrainOnce()
}
-@PublishedApi
+@JsonFriendModuleApi
@ExperimentalSerializationApi
-internal inline fun <reified T> Json.decodeToSequenceByReader(
- reader: SerialReader,
+public inline fun <reified T> decodeToSequenceByReader(
+ json: Json,
+ reader: InternalJsonReader,
format: DecodeSequenceMode = DecodeSequenceMode.AUTO_DETECT
-): Sequence<T> = decodeToSequenceByReader(reader, serializersModule.serializer(), format)
+): Sequence<T> = decodeToSequenceByReader(json, reader, json.serializersModule.serializer(), format)
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt
index 4df35de..a6a123c 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt
@@ -1,6 +1,6 @@
package kotlinx.serialization.json.internal
-internal expect class JsonToStringWriter constructor() : JsonWriter {
+internal expect class JsonToStringWriter constructor() : InternalJsonWriter {
override fun writeChar(char: Char)
override fun writeLong(value: Long)
override fun write(text: String)
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonTreeReader.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonTreeReader.kt
index 060c36b..9cb9bb3 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonTreeReader.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonTreeReader.kt
@@ -13,6 +13,7 @@
private val lexer: AbstractJsonLexer
) {
private val isLenient = configuration.isLenient
+ private val trailingCommaAllowed = configuration.allowTrailingComma
private var stackDepth = 0
private fun readObject(): JsonElement = readObjectImpl {
@@ -44,8 +45,9 @@
if (lastToken == TC_BEGIN_OBJ) { // Case of empty object
lexer.consumeNextToken(TC_END_OBJ)
} else if (lastToken == TC_COMMA) { // Trailing comma
- lexer.fail("Unexpected trailing comma")
- }
+ if (!trailingCommaAllowed) lexer.invalidTrailingComma()
+ lexer.consumeNextToken(TC_END_OBJ)
+ } // else unexpected token?
return JsonObject(result)
}
@@ -66,7 +68,8 @@
if (lastToken == TC_BEGIN_LIST) { // Case of empty object
lexer.consumeNextToken(TC_END_LIST)
} else if (lastToken == TC_COMMA) { // Trailing comma
- lexer.fail("Unexpected trailing comma")
+ if (!trailingCommaAllowed) lexer.invalidTrailingComma("array")
+ lexer.consumeNextToken(TC_END_LIST)
}
return JsonArray(result)
}
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/Polymorphic.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/Polymorphic.kt
index c1c9126..bd658fc 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/Polymorphic.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/Polymorphic.kt
@@ -63,20 +63,15 @@
val discriminator = deserializer.descriptor.classDiscriminator(json)
val jsonTree = cast<JsonObject>(decodeJsonElement(), deserializer.descriptor)
- val type = jsonTree[discriminator]?.jsonPrimitive?.content
- val actualSerializer = deserializer.findPolymorphicSerializerOrNull(this, type)
- ?: throwSerializerNotFound(type, jsonTree)
-
+ val type = jsonTree[discriminator]?.jsonPrimitive?.contentOrNull // differentiate between `"type":"null"` and `"type":null`.
@Suppress("UNCHECKED_CAST")
- return json.readPolymorphicJson(discriminator, jsonTree, actualSerializer as DeserializationStrategy<T>)
-}
-
-@JvmName("throwSerializerNotFound")
-internal fun throwSerializerNotFound(type: String?, jsonTree: JsonObject): Nothing {
- val suffix =
- if (type == null) "missing class discriminator ('null')"
- else "class discriminator '$type'"
- throw JsonDecodingException(-1, "Polymorphic serializer was not found for $suffix", jsonTree.toString())
+ val actualSerializer =
+ try {
+ deserializer.findPolymorphicSerializer(this, type)
+ } catch (it: SerializationException) { // Wrap SerializationException into JsonDecodingException to preserve input
+ throw JsonDecodingException(-1, it.message!!, jsonTree.toString())
+ } as DeserializationStrategy<T>
+ return json.readPolymorphicJson(discriminator, jsonTree, actualSerializer)
}
internal fun SerialDescriptor.classDiscriminator(json: Json): String {
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonDecoder.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonDecoder.kt
index 6cedabc..0018fce 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonDecoder.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonDecoder.kt
@@ -49,7 +49,6 @@
override fun decodeJsonElement(): JsonElement = JsonTreeReader(json.configuration, lexer).read()
- @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
try {
/*
@@ -72,19 +71,22 @@
val discriminator = deserializer.descriptor.classDiscriminator(json)
val type = lexer.peekLeadingMatchingValue(discriminator, configuration.isLenient)
- var actualSerializer: DeserializationStrategy<Any>? = null
- if (type != null) {
- actualSerializer = deserializer.findPolymorphicSerializerOrNull(this, type)
- }
- if (actualSerializer == null) {
- // Fallback if we haven't found discriminator or serializer
+ ?: // Fallback to slow path if we haven't found discriminator on first try
return decodeSerializableValuePolymorphic<T>(deserializer as DeserializationStrategy<T>)
- }
+
+ @Suppress("UNCHECKED_CAST")
+ val actualSerializer = try {
+ deserializer.findPolymorphicSerializer(this, type)
+ } catch (it: SerializationException) { // Wrap SerializationException into JsonDecodingException to preserve position, path, and input.
+ // Split multiline message from private core function:
+ // core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt:102
+ val message = it.message!!.substringBefore('\n').removeSuffix(".")
+ val hint = it.message!!.substringAfter('\n', missingDelimiterValue = "")
+ lexer.fail(message, hint = hint)
+ } as DeserializationStrategy<T>
discriminatorHolder = DiscriminatorHolder(discriminator)
- @Suppress("UNCHECKED_CAST")
- val result = actualSerializer.deserialize(this) as T
- return result
+ return actualSerializer.deserialize(this)
} catch (e: MissingFieldException) {
// Add "at path" if and only if we've just caught an exception and it hasn't been augmented yet
@@ -123,6 +125,7 @@
if (json.configuration.ignoreUnknownKeys && descriptor.elementsCount == 0) {
skipLeftoverElements(descriptor)
}
+ if (lexer.tryConsumeComma() && !json.configuration.allowTrailingComma) lexer.invalidTrailingComma("")
// First consume the object so we know it's correct
lexer.consumeNextToken(mode.end)
// Then cleanup the path
@@ -196,12 +199,12 @@
return if (lexer.canConsumeValue()) {
if (decodingKey) {
- if (currentIndex == -1) lexer.require(!hasComma) { "Unexpected trailing comma" }
+ if (currentIndex == -1) lexer.require(!hasComma) { "Unexpected leading comma" }
else lexer.require(hasComma) { "Expected comma after the key-value pair" }
}
++currentIndex
} else {
- if (hasComma) lexer.fail("Expected '}', but had ',' instead")
+ if (hasComma && !json.configuration.allowTrailingComma) lexer.invalidTrailingComma()
CompositeDecoder.DECODE_DONE
}
}
@@ -240,7 +243,7 @@
hasComma = handleUnknown(key)
}
}
- if (hasComma) lexer.fail("Unexpected trailing comma")
+ if (hasComma && !json.configuration.allowTrailingComma) lexer.invalidTrailingComma()
return elementMarker?.nextUnmarkedIndex() ?: CompositeDecoder.DECODE_DONE
}
@@ -263,28 +266,19 @@
if (currentIndex != -1 && !hasComma) lexer.fail("Expected end of the array or comma")
++currentIndex
} else {
- if (hasComma) lexer.fail("Unexpected trailing comma")
+ if (hasComma && !json.configuration.allowTrailingComma) lexer.invalidTrailingComma("array")
CompositeDecoder.DECODE_DONE
}
}
-
+ /*
+ * The primitives are allowed to be quoted and unquoted
+ * to simplify map key parsing and integrations with third-party API.
+ */
override fun decodeBoolean(): Boolean {
- /*
- * We prohibit any boolean literal that is not strictly 'true' or 'false' as it is considered way too
- * error-prone, but allow quoted literal in relaxed mode for booleans.
- */
- return if (configuration.isLenient) {
- lexer.consumeBooleanLenient()
- } else {
- lexer.consumeBoolean()
- }
+ return lexer.consumeBooleanLenient()
}
- /*
- * The rest of the primitives are allowed to be quoted and unquoted
- * to simplify integrations with third-party API.
- */
override fun decodeByte(): Byte {
val value = lexer.consumeNumericLiteral()
// Check for overflow
@@ -359,13 +353,14 @@
}
}
-@InternalSerializationApi
-public fun <T> Json.decodeStringToJsonTree(
+@JsonFriendModuleApi // used in json-tests
+public fun <T> decodeStringToJsonTree(
+ json: Json,
deserializer: DeserializationStrategy<T>,
source: String
): JsonElement {
val lexer = StringJsonLexer(source)
- val input = StreamingJsonDecoder(this, WriteMode.OBJ, lexer, deserializer.descriptor, null)
+ val input = StreamingJsonDecoder(json, WriteMode.OBJ, lexer, deserializer.descriptor, null)
val tree = input.decodeJsonElement()
lexer.expectEof()
return tree
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonEncoder.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonEncoder.kt
index c6f76cb..4f7b1ec 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonEncoder.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonEncoder.kt
@@ -10,7 +10,6 @@
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*
-import kotlin.native.concurrent.*
private val unsignedNumberDescriptors = setOf(
UInt.serializer().descriptor,
@@ -34,7 +33,7 @@
) : JsonEncoder, AbstractEncoder() {
internal constructor(
- output: JsonWriter, json: Json, mode: WriteMode,
+ output: InternalJsonWriter, json: Json, mode: WriteMode,
modeReuseCache: Array<JsonEncoder?>
) : this(Composer(output, json), json, mode, modeReuseCache)
@@ -164,7 +163,7 @@
else -> super.encodeInline(descriptor)
}
- private inline fun <reified T: Composer> composerAs(composerCreator: (writer: JsonWriter, forceQuoting: Boolean) -> T): T {
+ private inline fun <reified T: Composer> composerAs(composerCreator: (writer: InternalJsonWriter, forceQuoting: Boolean) -> T): T {
// If we're inside encodeInline().encodeSerializableValue, we should preserve the forceQuoting state
// inside the composer, but not in the encoder (otherwise we'll get into `if (forceQuoting) encodeString(value.toString())` part
// and unsigned numbers would be encoded incorrectly)
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/StringOps.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/StringOps.kt
index f488d90..ed76ba0 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/StringOps.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/StringOps.kt
@@ -12,7 +12,6 @@
else (d - 10 + 'a'.code).toChar()
}
-@PublishedApi
internal val ESCAPE_STRINGS: Array<String?> = arrayOfNulls<String>(93).apply {
for (c in 0..0x1f) {
val c1 = toHexChar(c shr 12)
@@ -68,4 +67,4 @@
this.equals("true", ignoreCase = true) -> true
this.equals("false", ignoreCase = true) -> false
else -> null
-}
\ No newline at end of file
+}
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt
index 40a3e41..aedfb95 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt
@@ -15,12 +15,12 @@
import kotlinx.serialization.modules.*
import kotlin.jvm.*
-@InternalSerializationApi
-public fun <T> Json.readJson(element: JsonElement, deserializer: DeserializationStrategy<T>): T {
+@JsonFriendModuleApi
+public fun <T> readJson(json: Json, element: JsonElement, deserializer: DeserializationStrategy<T>): T {
val input = when (element) {
- is JsonObject -> JsonTreeDecoder(this, element)
- is JsonArray -> JsonTreeListDecoder(this, element)
- is JsonLiteral, JsonNull -> JsonPrimitiveDecoder(this, element as JsonPrimitive)
+ is JsonObject -> JsonTreeDecoder(json, element)
+ is JsonArray -> JsonTreeListDecoder(json, element)
+ is JsonLiteral, JsonNull -> JsonPrimitiveDecoder(json, element as JsonPrimitive)
}
return input.decodeSerializableValue(deserializer)
}
@@ -91,16 +91,7 @@
override fun decodeTaggedNotNullMark(tag: String): Boolean = currentElement(tag) !== JsonNull
override fun decodeTaggedBoolean(tag: String): Boolean {
- val value = getPrimitiveValue(tag)
- if (!json.configuration.isLenient) {
- val literal = value.asLiteral("boolean")
- if (literal.isString) throw JsonDecodingException(
- -1, "Boolean literal for key '$tag' should be unquoted.\n$lenientHint", currentObject().toString()
- )
- }
- return value.primitive("boolean") {
- booleanOrNull ?: throw IllegalArgumentException() /* Will be handled by 'primitive' */
- }
+ return getPrimitiveValue(tag).primitive("boolean", JsonPrimitive::booleanOrNull)
}
override fun decodeTaggedByte(tag: String) = getPrimitiveValue(tag).primitive("byte") {
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonEncoder.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonEncoder.kt
index e42d425..5e3c808 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonEncoder.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonEncoder.kt
@@ -14,10 +14,10 @@
import kotlin.collections.set
import kotlin.jvm.*
-@InternalSerializationApi
-public fun <T> Json.writeJson(value: T, serializer: SerializationStrategy<T>): JsonElement {
+@JsonFriendModuleApi
+public fun <T> writeJson(json: Json, value: T, serializer: SerializationStrategy<T>): JsonElement {
lateinit var result: JsonElement
- val encoder = JsonTreeEncoder(this) { result = it }
+ val encoder = JsonTreeEncoder(json) { result = it }
encoder.encodeSerializableValue(serializer, value)
return result
}
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/lexer/AbstractJsonLexer.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/lexer/AbstractJsonLexer.kt
index a1a6a5e..c83bdef 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/lexer/AbstractJsonLexer.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/lexer/AbstractJsonLexer.kt
@@ -149,7 +149,7 @@
protected abstract val source: CharSequence
@JvmField
- protected var currentPosition: Int = 0 // position in source
+ internal var currentPosition: Int = 0 // position in source
@JvmField
val path = JsonPath()
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/lexer/ReaderJsonLexer.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/lexer/ReaderJsonLexer.kt
index 3c785f8..24e5b47 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/lexer/ReaderJsonLexer.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/lexer/ReaderJsonLexer.kt
@@ -2,8 +2,6 @@
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
-
package kotlinx.serialization.json.internal
internal const val BATCH_SIZE: Int = 16 * 1024
@@ -13,7 +11,7 @@
* For some reason this hand-rolled implementation is faster than
* fun ArrayAsSequence(s: CharArray): CharSequence = java.nio.CharBuffer.wrap(s, 0, length)
*/
-internal class ArrayAsSequence(private val buffer: CharArray) : CharSequence {
+internal class ArrayAsSequence(internal val buffer: CharArray) : CharSequence {
override var length: Int = buffer.size
override fun get(index: Int): Char = buffer[index]
@@ -35,7 +33,7 @@
}
internal class ReaderJsonLexer(
- private val reader: SerialReader,
+ private val reader: InternalJsonReader,
private val buffer: CharArray = CharArrayPoolBatchSize.take()
) : AbstractJsonLexer() {
private var threshold: Int = DEFAULT_THRESHOLD // chars
diff --git a/formats/json/jsMain/src/kotlinx/serialization/json/JsonSchemaCache.kt b/formats/json/jsWasmMain/src/kotlinx/serialization/json/JsonSchemaCache.kt
similarity index 100%
rename from formats/json/jsMain/src/kotlinx/serialization/json/JsonSchemaCache.kt
rename to formats/json/jsWasmMain/src/kotlinx/serialization/json/JsonSchemaCache.kt
diff --git a/formats/json/jsMain/src/kotlinx/serialization/json/internal/CharArrayPool.kt b/formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/CharArrayPool.kt
similarity index 100%
rename from formats/json/jsMain/src/kotlinx/serialization/json/internal/CharArrayPool.kt
rename to formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/CharArrayPool.kt
diff --git a/formats/json/jsMain/src/kotlinx/serialization/json/internal/FormatLanguageJs.kt b/formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/FormatLanguageJsWasm.kt
similarity index 100%
rename from formats/json/jsMain/src/kotlinx/serialization/json/internal/FormatLanguageJs.kt
rename to formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/FormatLanguageJsWasm.kt
diff --git a/formats/json/jsMain/src/kotlinx/serialization/json/internal/JsonToStringWriterJs.kt b/formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/JsonToStringWriterJsWasm.kt
similarity index 95%
rename from formats/json/jsMain/src/kotlinx/serialization/json/internal/JsonToStringWriterJs.kt
rename to formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/JsonToStringWriterJsWasm.kt
index 806a1d8..387ed7e 100644
--- a/formats/json/jsMain/src/kotlinx/serialization/json/internal/JsonToStringWriterJs.kt
+++ b/formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/JsonToStringWriterJsWasm.kt
@@ -1,6 +1,6 @@
package kotlinx.serialization.json.internal
-internal actual open class JsonToStringWriter actual constructor(): JsonWriter {
+internal actual open class JsonToStringWriter actual constructor(): InternalJsonWriter {
private val sb = StringBuilder(128)
actual override fun writeLong(value: Long) {
diff --git a/formats/json/jsMain/src/kotlinx/serialization/json/internal/createMapForCache.kt b/formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/createMapForCache.kt
similarity index 100%
rename from formats/json/jsMain/src/kotlinx/serialization/json/internal/createMapForCache.kt
rename to formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/createMapForCache.kt
diff --git a/formats/json/jvmMain/src/kotlinx/serialization/json/JvmStreams.kt b/formats/json/jvmMain/src/kotlinx/serialization/json/JvmStreams.kt
index 4af1045..329e309 100644
--- a/formats/json/jvmMain/src/kotlinx/serialization/json/JvmStreams.kt
+++ b/formats/json/jvmMain/src/kotlinx/serialization/json/JvmStreams.kt
@@ -22,7 +22,7 @@
) {
val writer = JsonToJavaStreamWriter(stream)
try {
- encodeByWriter(writer, serializer, value)
+ encodeByWriter(this, writer, serializer, value)
} finally {
writer.release()
}
@@ -58,7 +58,7 @@
): T {
val reader = JavaStreamSerialReader(stream)
try {
- return decodeByReader(deserializer, reader)
+ return decodeByReader(this, deserializer, reader)
} finally {
reader.release()
}
@@ -102,7 +102,7 @@
deserializer: DeserializationStrategy<T>,
format: DecodeSequenceMode = DecodeSequenceMode.AUTO_DETECT
): Sequence<T> {
- return decodeToSequenceByReader(JavaStreamSerialReader(stream), deserializer, format)
+ return decodeToSequenceByReader(this, JavaStreamSerialReader(stream), deserializer, format)
}
/**
diff --git a/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt b/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt
index e11c21c..5666724 100644
--- a/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt
+++ b/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt
@@ -25,7 +25,7 @@
* 3) We pool char arrays in order to save excess resizes, allocations
* and nulls-out of arrays.
*/
-internal actual class JsonToStringWriter : JsonWriter {
+internal actual class JsonToStringWriter : InternalJsonWriter {
private var array: CharArray = CharArrayPool.take()
private var size = 0
diff --git a/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JvmJsonStreams.kt b/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JvmJsonStreams.kt
index 274dd4d..e8e0d9a 100644
--- a/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JvmJsonStreams.kt
+++ b/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JvmJsonStreams.kt
@@ -3,7 +3,7 @@
import java.io.InputStream
import java.io.OutputStream
-internal class JsonToJavaStreamWriter(private val stream: OutputStream) : JsonWriter {
+internal class JsonToJavaStreamWriter(private val stream: OutputStream) : InternalJsonWriter {
private val buffer = ByteArrayPool.take()
private var charArray = CharArrayPool.take()
private var indexInBuffer: Int = 0
@@ -253,7 +253,7 @@
}
}
-internal class JavaStreamSerialReader(stream: InputStream) : SerialReader {
+internal class JavaStreamSerialReader(stream: InputStream) : InternalJsonReader {
// NB: not closed on purpose, it is the responsibility of the caller
private val reader = CharsetReader(stream, Charsets.UTF_8)
diff --git a/formats/json/nativeMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt b/formats/json/nativeMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt
index d996fc3..137f3bc 100644
--- a/formats/json/nativeMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt
+++ b/formats/json/nativeMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt
@@ -1,6 +1,6 @@
package kotlinx.serialization.json.internal
-internal actual open class JsonToStringWriter actual constructor(): JsonWriter {
+internal actual open class JsonToStringWriter actual constructor(): InternalJsonWriter {
private val sb = StringBuilder(128)
actual override fun writeLong(value: Long) {
diff --git a/formats/protobuf/build.gradle b/formats/protobuf/build.gradle
index c2183b2..9f93b18 100644
--- a/formats/protobuf/build.gradle
+++ b/formats/protobuf/build.gradle
@@ -21,8 +21,11 @@
}
kotlin {
-
sourceSets {
+ configureEach {
+ languageSettings.optIn("kotlinx.serialization.internal.CoreFriendModuleApi")
+ }
+
commonMain {
dependencies {
api project(":kotlinx-serialization-core")
diff --git a/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufDecoding.kt b/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufDecoding.kt
index d2a28b5..861e2bf 100644
--- a/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufDecoding.kt
+++ b/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufDecoding.kt
@@ -3,7 +3,7 @@
*/
@file:OptIn(ExperimentalSerializationApi::class)
-@file:Suppress("UNCHECKED_CAST", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+@file:Suppress("UNCHECKED_CAST")
package kotlinx.serialization.protobuf.internal
@@ -213,7 +213,7 @@
val mapEntrySerial =
kotlinx.serialization.builtins.MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
val oldSet = (previousValue as? Map<Any?, Any?>)?.entries
- val setOfEntries = LinkedHashSetSerializer(mapEntrySerial).merge(this, oldSet)
+ val setOfEntries = (SetSerializer(mapEntrySerial) as AbstractCollectionSerializer<Map.Entry<Any?, Any?>, Set<Map.Entry<Any?, Any?>>, *>).merge(this, oldSet)
return setOfEntries.associateBy({ it.key }, { it.value }) as T
}
diff --git a/formats/protobuf/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt b/formats/protobuf/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt
index 2652d5c..e746679 100644
--- a/formats/protobuf/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt
+++ b/formats/protobuf/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt
@@ -5,7 +5,7 @@
package kotlinx.serialization.test
enum class Platform {
- JVM, JS, NATIVE
+ JVM, JS, NATIVE, WASM
}
public expect val currentPlatform: Platform
@@ -14,3 +14,4 @@
public fun isJvm(): Boolean = currentPlatform == Platform.JVM
public fun isNative(): Boolean = currentPlatform == Platform.NATIVE
+public fun isWasm(): Boolean = currentPlatform == Platform.WASM
\ No newline at end of file
diff --git a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3EnumTest.kt b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3EnumTest.kt
new file mode 100644
index 0000000..7b2dda2
--- /dev/null
+++ b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3EnumTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.protobuf.conformance
+
+import com.google.protobuf_test_messages.proto3.*
+import kotlinx.serialization.*
+import kotlinx.serialization.protobuf.*
+import kotlin.test.*
+
+@Serializable
+data class KTestMessagesProto3Enum(
+ @ProtoNumber(21) val optionalNestedEnum: KNestedEnum = KNestedEnum.FOO,
+ @ProtoNumber(22) val optionalForeignEnum: KForeignEnum = KForeignEnum.FOREIGN_FOO,
+ @ProtoNumber(23) val optionalAliasedEnum: KAliasedEnum = KAliasedEnum.ALIAS_FOO,
+) {
+ enum class KNestedEnum {
+ @ProtoNumber(0)
+ FOO,
+
+ @ProtoNumber(1)
+ BAR,
+
+ @ProtoNumber(2)
+ BAZ,
+
+ @ProtoNumber(-1)
+ NEG;
+
+ fun toProto() = TestMessagesProto3.TestAllTypesProto3.NestedEnum.valueOf(this.name)
+ }
+
+
+ enum class KAliasedEnum {
+ @ProtoNumber(0)
+ ALIAS_FOO,
+
+ @ProtoNumber(1)
+ ALIAS_BAR,
+
+ @ProtoNumber(2)
+ ALIAS_BAZ,
+
+ @ProtoNumber(2)
+ MOO,
+
+ @ProtoNumber(2)
+ moo,
+
+ @ProtoNumber(2)
+ bAz;
+
+ fun toProto() = TestMessagesProto3.TestAllTypesProto3.AliasedEnum.valueOf(this.name)
+ }
+}
+
+enum class KForeignEnum {
+ @ProtoNumber(0)
+ FOREIGN_FOO,
+
+ @ProtoNumber(1)
+ FOREIGN_BAR,
+
+ @ProtoNumber(2)
+ FOREIGN_BAZ;
+
+ fun toProto() = TestMessagesProto3.ForeignEnum.valueOf(this.name)
+}
+
+class Proto3EnumTest {
+ @Test
+ fun default() {
+ val message = KTestMessagesProto3Enum(
+ optionalNestedEnum = KTestMessagesProto3Enum.KNestedEnum.NEG,
+ optionalForeignEnum = KForeignEnum.FOREIGN_BAR,
+ optionalAliasedEnum = KTestMessagesProto3Enum.KAliasedEnum.ALIAS_BAR
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+ assertEquals(message.optionalNestedEnum.toProto(), restored.optionalNestedEnum)
+ assertEquals(message.optionalForeignEnum.toProto(), restored.optionalForeignEnum)
+ assertEquals(message.optionalAliasedEnum.toProto(), restored.optionalAliasedEnum)
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Enum>(restored.toByteArray())
+ assertEquals(message, restoredMessage)
+ }
+}
diff --git a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3MapTest.kt b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3MapTest.kt
new file mode 100644
index 0000000..a961424
--- /dev/null
+++ b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3MapTest.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.protobuf.conformance
+
+import com.google.protobuf_test_messages.proto3.*
+import io.kotlintest.properties.*
+import kotlinx.serialization.*
+import kotlinx.serialization.protobuf.*
+import kotlin.test.*
+
+@Serializable
+data class KTestMessagesProto3Map(
+ @ProtoNumber(56) val mapInt32Int32: Map<Int, Int> = emptyMap(),
+ @ProtoNumber(57) val mapInt64Int64: Map<Long, Long> = emptyMap(),
+ @ProtoNumber(58) val mapUint32Uint32: Map<UInt, UInt> = emptyMap(),
+ @ProtoNumber(59) val mapUint64Uint64: Map<ULong, ULong> = emptyMap(),
+ @ProtoNumber(60) val mapSint32Sint32: Map<Int, Int> = emptyMap(),
+ @ProtoNumber(61) val mapSint64Sint64: Map<Long, Long> = emptyMap(),
+ @ProtoNumber(62) val mapFixed32Fixed32: Map<Int, Int> = emptyMap(),
+ @ProtoNumber(63) val mapFixed64Fixed64: Map<Long, Long> = emptyMap(),
+ @ProtoNumber(64) val mapSfixed32Sfixed32: Map<Int, Int> = emptyMap(),
+ @ProtoNumber(65) val mapSfixed64Sfixed64: Map<Long, Long> = emptyMap(),
+ @ProtoNumber(66) val mapInt32Float: Map<Int, Float> = emptyMap(),
+ @ProtoNumber(67) val mapInt32Double: Map<Int, Double> = emptyMap(),
+ @ProtoNumber(68) val mapBoolBool: Map<Boolean, Boolean> = emptyMap(),
+ @ProtoNumber(69) val mapStringString: Map<String, String> = emptyMap(),
+ @ProtoNumber(70) val mapStringBytes: Map<String, ByteArray> = emptyMap(),
+ @ProtoNumber(71) val mapStringNestedMessage: Map<String, KTestMessagesProto3Message.KNestedMessage> = emptyMap(),
+ @ProtoNumber(72) val mapStringForeignMessage: Map<String, KForeignMessage> = emptyMap(),
+ @ProtoNumber(73) val mapStringNestedEnum: Map<String, KTestMessagesProto3Enum.KNestedEnum> = emptyMap(),
+ @ProtoNumber(74) val mapStringForeignEnum: Map<String, KForeignEnum> = emptyMap(),
+)
+
+class Proto3MapTest {
+ @Test
+ fun default() {
+ val message = KTestMessagesProto3Map(
+ mapInt32Int32 = Gen.map(Gen.int(), Gen.int()).generate(),
+ mapInt64Int64 = Gen.map(Gen.long(), Gen.long()).generate(),
+ mapUint32Uint32 = Gen.map(Gen.int().map { it.toUInt() }, Gen.int().map { it.toUInt() }).generate(),
+ mapUint64Uint64 = Gen.map(Gen.int().map { it.toULong() }, Gen.int().map { it.toULong() }).generate(),
+ mapInt32Float = Gen.map(Gen.int(), Gen.float()).generate(),
+ mapInt32Double = Gen.map(Gen.int(), Gen.double()).generate(),
+ mapBoolBool = Gen.map(Gen.bool(), Gen.bool()).generate(),
+ mapStringString = Gen.map(Gen.string(), Gen.string()).generate(),
+ mapStringBytes = Gen.map(Gen.string(), Gen.string().map { it.toByteArray() }).generate(),
+ mapStringNestedMessage = mapOf(
+ "asd_1" to KTestMessagesProto3Message.KNestedMessage(
+ 1,
+ null
+ ),
+ "asi_#" to KTestMessagesProto3Message.KNestedMessage(
+ 2,
+ KTestMessagesProto3Message(
+ KTestMessagesProto3Message.KNestedMessage(3, null),
+ )
+ )
+ ),
+ mapStringForeignMessage = mapOf(
+ "" to KForeignMessage(1),
+ "-2" to KForeignMessage(-12),
+ ),
+ mapStringNestedEnum = Gen.map(
+ Gen.string(), Gen.oneOf(
+ KTestMessagesProto3Enum.KNestedEnum.entries,
+ )
+ ).generate(),
+ mapStringForeignEnum = Gen.map(
+ Gen.string(), Gen.oneOf(
+ KForeignEnum.entries,
+ )
+ ).generate(),
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+
+ assertEquals(message.mapInt32Int32, restored.mapInt32Int32Map)
+ assertEquals(message.mapInt64Int64, restored.mapInt64Int64Map)
+ assertEquals(
+ message.mapUint32Uint32,
+ restored.mapUint32Uint32Map.map { it.key.toUInt() to it.value.toUInt() }.toMap()
+ )
+ assertEquals(
+ message.mapUint64Uint64,
+ restored.mapUint64Uint64Map.map { it.key.toULong() to it.value.toULong() }.toMap()
+ )
+ assertEquals(message.mapInt32Float, restored.mapInt32FloatMap)
+ assertEquals(message.mapInt32Double, restored.mapInt32DoubleMap)
+ assertEquals(message.mapBoolBool, restored.mapBoolBoolMap)
+ assertEquals(message.mapStringString, restored.mapStringStringMap)
+ assertContentEquals(
+ message.mapStringBytes.mapValues { it.value.toString(Charsets.UTF_32) }.entries.toList(),
+ restored.mapStringBytesMap.mapValues { it.value.toByteArray().toString(Charsets.UTF_32) }.entries.toList()
+ )
+ assertEquals(
+ message.mapStringNestedMessage.mapValues { it.value.toProto() },
+ restored.mapStringNestedMessageMap
+ )
+ assertEquals(
+ message.mapStringForeignMessage.mapValues { it.value.toProto() },
+ restored.mapStringForeignMessageMap
+ )
+ assertEquals(
+ message.mapStringNestedEnum.mapValues { it.value.name },
+ restored.mapStringNestedEnumMap.mapValues { it.value.name },
+ )
+ assertEquals(
+ message.mapStringForeignEnum.mapValues { it.value.name },
+ restored.mapStringForeignEnumMap.mapValues { it.value.name }
+ )
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Map>(restored.toByteArray())
+ assertEquals(message.copy(mapStringBytes = mapOf()), restoredMessage.copy(mapStringBytes = mapOf()))
+ }
+
+ @Test
+ @Ignore
+ // Issue: https://github.com/Kotlin/kotlinx.serialization/issues/2417
+ fun signedAndFixed() {
+ val message = KTestMessagesProto3Map(
+ mapSint32Sint32 = Gen.map(Gen.int(), Gen.int()).generate(),
+ mapSint64Sint64 = Gen.map(Gen.long(), Gen.long()).generate(),
+ mapFixed32Fixed32 = Gen.map(Gen.int(), Gen.int()).generate(),
+ mapFixed64Fixed64 = Gen.map(Gen.long(), Gen.long()).generate(),
+ mapSfixed32Sfixed32 = Gen.map(Gen.int(), Gen.int()).generate(),
+ mapSfixed64Sfixed64 = Gen.map(Gen.long(), Gen.long()).generate(),
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+
+ assertContentEquals(message.mapSint32Sint32.entries.toList(), restored.mapSint32Sint32Map.entries.toList())
+ assertContentEquals(message.mapSint64Sint64.entries.toList(), restored.mapSint64Sint64Map.entries.toList())
+ assertContentEquals(message.mapFixed32Fixed32.entries.toList(), restored.mapFixed32Fixed32Map.entries.toList())
+ assertContentEquals(message.mapFixed64Fixed64.entries.toList(), restored.mapFixed64Fixed64Map.entries.toList())
+ assertContentEquals(
+ message.mapSfixed32Sfixed32.entries.toList(),
+ restored.mapSfixed32Sfixed32Map.entries.toList()
+ )
+ assertContentEquals(
+ message.mapSfixed64Sfixed64.entries.toList(),
+ restored.mapSfixed64Sfixed64Map.entries.toList()
+ )
+
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Map>(restored.toByteArray())
+ assertEquals(message, restoredMessage)
+ }
+}
diff --git a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3MessageTest.kt b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3MessageTest.kt
new file mode 100644
index 0000000..c369d6e
--- /dev/null
+++ b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3MessageTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.protobuf.conformance
+
+import com.google.protobuf_test_messages.proto3.*
+import kotlinx.serialization.*
+import kotlinx.serialization.protobuf.*
+import kotlin.test.*
+
+@Serializable
+data class KTestMessagesProto3Message(
+ @ProtoNumber(18) val optionalNestedMessage: KNestedMessage? = null,
+ @ProtoNumber(19) val optionalForeignMessage: KForeignMessage? = null,
+) {
+ @Serializable
+ data class KNestedMessage(
+ @ProtoNumber(1) val a: Int = 0,
+ @ProtoNumber(2) val corecursive: KTestMessagesProto3Message? = null,
+ ) {
+ fun toProto(): TestMessagesProto3.TestAllTypesProto3.NestedMessage =
+ TestMessagesProto3.TestAllTypesProto3.NestedMessage.parseFrom(
+ ProtoBuf.encodeToByteArray(this)
+ )
+ }
+}
+
+@Serializable
+data class KForeignMessage(
+ @ProtoNumber(1) val c: Int = 0,
+) {
+ fun toProto(): TestMessagesProto3.ForeignMessage =
+ TestMessagesProto3.ForeignMessage.parseFrom(
+ ProtoBuf.encodeToByteArray(this)
+ )
+}
+
+class Proto3MessageTest {
+ @Test
+ fun default() {
+ val message = KTestMessagesProto3Message(
+ optionalNestedMessage = KTestMessagesProto3Message.KNestedMessage(
+ a = 150,
+ corecursive = KTestMessagesProto3Message(
+ optionalNestedMessage = KTestMessagesProto3Message.KNestedMessage(
+ a = 42,
+ )
+ )
+ ),
+ optionalForeignMessage = KForeignMessage(
+ c = 150,
+ )
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+ assertEquals(message.optionalNestedMessage?.a, restored.optionalNestedMessage.a)
+ assertEquals(
+ message.optionalNestedMessage?.corecursive?.optionalNestedMessage?.a,
+ restored.optionalNestedMessage.corecursive.optionalNestedMessage.a
+ )
+ assertEquals(message.optionalForeignMessage?.c, restored.optionalForeignMessage.c)
+ }
+}
diff --git a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3OneofTest.kt b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3OneofTest.kt
new file mode 100644
index 0000000..fda811e
--- /dev/null
+++ b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3OneofTest.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.protobuf.conformance
+
+import com.google.protobuf_test_messages.proto3.*
+import kotlinx.serialization.*
+import kotlinx.serialization.protobuf.*
+import kotlin.test.*
+
+@Serializable
+data class KTestMessageProto3Oneof(
+ @ProtoNumber(111) val oneofUint32: UInt? = null,
+ @ProtoNumber(112) val oneofNestedMessage: KTestMessagesProto3Message.KNestedMessage? = null,
+ @ProtoNumber(113) val oneofString: String? = null,
+ @ProtoNumber(114) val oneofBytes: ByteArray? = null,
+ @ProtoNumber(115) val oneofBool: Boolean? = null,
+ @ProtoNumber(116) val oneofUint64: ULong? = null,
+ @ProtoNumber(117) val oneofFloat: Float? = null,
+ @ProtoNumber(118) val oneofDouble: Double? = null,
+ @ProtoNumber(119) val oneofEnum: KTestMessagesProto3Enum.KNestedEnum? = null,
+) {
+ init {
+ require(
+ listOf(
+ oneofUint32,
+ oneofNestedMessage,
+ oneofString,
+ oneofBytes,
+ oneofBool,
+ oneofUint64,
+ oneofFloat,
+ oneofDouble,
+ oneofEnum,
+ ).count { it != null } == 1
+ )
+ }
+}
+
+class Proto3OneofTest {
+
+ /**
+ * Verify that the given [KTestMessageProto3Oneof] is correctly encoded and decoded as
+ * [TestMessagesProto3.TestAllTypesProto3] by running the [verificationFunction]. This
+ * method also verifies that the encoded and decoded message is equal to the original message.
+ *
+ * @param verificationFunction a function that verifies the encoded and decoded message. First parameter
+ * is the original message and the second parameter is the decoded protobuf library message.
+ * @receiver the [KTestMessageProto3Oneof] to verify
+ */
+ private fun KTestMessageProto3Oneof.verify(
+ verificationFunction: (KTestMessageProto3Oneof, TestMessagesProto3.TestAllTypesProto3) -> Unit,
+ ) {
+ val bytes = ProtoBuf.encodeToByteArray(this)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+ verificationFunction.invoke(this, restored)
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessageProto3Oneof>(restored.toByteArray())
+
+ // [equals] method is not implemented for [ByteArray] so we need to compare it separately.
+ assertEquals(this, restoredMessage.copy(oneofBytes = this.oneofBytes))
+ assertContentEquals(this.oneofBytes, restoredMessage.oneofBytes)
+ }
+
+ @Test
+ fun uint32() {
+ KTestMessageProto3Oneof(oneofUint32 = 150u).verify { self, restored ->
+ assertEquals(self.oneofUint32, restored.oneofUint32.toUInt())
+ }
+ }
+
+ @Test
+ fun nestedMessage() {
+ KTestMessageProto3Oneof(
+ oneofNestedMessage = KTestMessagesProto3Message.KNestedMessage(a = 150),
+ ).verify { self, restored ->
+ assertEquals(self.oneofNestedMessage?.a, restored.oneofNestedMessage.a)
+ }
+ }
+
+ @Test
+ fun string() {
+ KTestMessageProto3Oneof(oneofString = "150").verify { self, restored ->
+ assertEquals(self.oneofString, restored.oneofString)
+ }
+ }
+
+ @Test
+ fun bytes() {
+ KTestMessageProto3Oneof(oneofBytes = "150".toByteArray()).verify { self, restored ->
+ assertContentEquals(self.oneofBytes, restored.oneofBytes.toByteArray())
+ }
+ }
+
+ @Test
+ fun bool() {
+ KTestMessageProto3Oneof(oneofBool = true).verify { self, restored ->
+ assertEquals(self.oneofBool, restored.oneofBool)
+ }
+ }
+
+ @Test
+ fun uint64() {
+ KTestMessageProto3Oneof(oneofUint64 = 150uL).verify { self, restored ->
+ assertEquals(self.oneofUint64, restored.oneofUint64.toULong())
+ }
+ }
+
+ @Test
+ fun float() {
+ KTestMessageProto3Oneof(oneofFloat = 150f).verify { self, restored ->
+ assertEquals(self.oneofFloat, restored.oneofFloat)
+ }
+ }
+
+ @Test
+ fun double() {
+ KTestMessageProto3Oneof(oneofDouble = 150.0).verify { self, restored ->
+ assertEquals(self.oneofDouble, restored.oneofDouble)
+ }
+ }
+
+ @Test
+ fun enum() {
+ KTestMessageProto3Oneof(oneofEnum = KTestMessagesProto3Enum.KNestedEnum.BAR).verify { self, restored ->
+ assertEquals(self.oneofEnum?.name, restored.oneofEnum.name)
+ }
+ }
+}
diff --git a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3PackedTest.kt b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3PackedTest.kt
new file mode 100644
index 0000000..e0da0bb
--- /dev/null
+++ b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3PackedTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.protobuf.conformance
+
+import com.google.protobuf_test_messages.proto3.*
+import io.kotlintest.properties.*
+import kotlinx.serialization.*
+import kotlinx.serialization.protobuf.*
+import kotlin.test.*
+
+@Serializable
+data class KTestMessagesProto3Packed(
+ @ProtoNumber(75) @ProtoPacked val packedInt32: List<Int> = emptyList(),
+ @ProtoNumber(76) @ProtoPacked val packedInt64: List<Long> = emptyList(),
+ @ProtoNumber(77) @ProtoPacked val packedUint32: List<UInt> = emptyList(),
+ @ProtoNumber(78) @ProtoPacked val packedUint64: List<ULong> = emptyList(),
+ @ProtoNumber(79) @ProtoPacked val packedSint32: List<Int> = emptyList(),
+ @ProtoNumber(80) @ProtoPacked val packedSint64: List<Long> = emptyList(),
+ @ProtoNumber(81) @ProtoPacked val packedFixed32: List<Int> = emptyList(),
+ @ProtoNumber(82) @ProtoPacked val packedFixed64: List<Long> = emptyList(),
+ @ProtoNumber(83) @ProtoPacked val packedSfixed32: List<Int> = emptyList(),
+ @ProtoNumber(84) @ProtoPacked val packedSfixed64: List<Long> = emptyList(),
+ @ProtoNumber(85) @ProtoPacked val packedFloat: List<Float> = emptyList(),
+ @ProtoNumber(86) @ProtoPacked val packedDouble: List<Double> = emptyList(),
+ @ProtoNumber(87) @ProtoPacked val packedBool: List<Boolean> = emptyList(),
+)
+
+class Proto3PackedTest {
+ @Test
+ fun default() {
+ val message = KTestMessagesProto3Packed(
+ packedInt32 = Gen.list(Gen.int()).generate(),
+ packedInt64 = Gen.list(Gen.long()).generate(),
+ packedFloat = Gen.list(Gen.float()).generate(),
+ packedDouble = Gen.list(Gen.double()).generate(),
+ packedBool = Gen.list(Gen.bool()).generate(),
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+ assertEquals(message.packedInt32, restored.packedInt32List)
+ assertEquals(message.packedInt64, restored.packedInt64List)
+ assertEquals(message.packedFloat, restored.packedFloatList)
+ assertEquals(message.packedDouble, restored.packedDoubleList)
+ assertEquals(message.packedBool, restored.packedBoolList)
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Packed>(restored.toByteArray())
+ assertEquals(message, restoredMessage)
+ }
+
+ @Test
+ @Ignore
+ // Issue: https://github.com/Kotlin/kotlinx.serialization/issues/2419
+ fun signedAndFixed() {
+ val message = KTestMessagesProto3Packed(
+ packedSint32 = Gen.list(Gen.int()).generate(),
+ packedSint64 = Gen.list(Gen.long()).generate(),
+ packedFixed32 = Gen.list(Gen.int()).generate(),
+ packedFixed64 = Gen.list(Gen.long()).generate(),
+ packedSfixed32 = Gen.list(Gen.int()).generate(),
+ packedSfixed64 = Gen.list(Gen.long()).generate(),
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+ assertEquals(message.packedSint32, restored.packedSint32List)
+ assertEquals(message.packedSint64, restored.packedSint64List)
+ assertEquals(message.packedFixed32, restored.packedFixed32List)
+ assertEquals(message.packedFixed64, restored.packedFixed64List)
+ assertEquals(message.packedSfixed32, restored.packedSfixed32List)
+ assertEquals(message.packedSfixed64, restored.packedSfixed64List)
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Packed>(restored.toByteArray())
+ assertEquals(message, restoredMessage)
+ }
+
+ @Test
+ @Ignore
+ // Issue: https://github.com/Kotlin/kotlinx.serialization/issues/2418
+ fun unsigned() {
+ val message = KTestMessagesProto3Packed(
+ packedUint32 = Gen.list(Gen.int().map { it.toUInt() }).generate(),
+ packedUint64 = Gen.list(Gen.long().map { it.toULong() }).generate(),
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+ assertEquals(message.packedUint32, restored.packedUint32List.map { it.toUInt() })
+ assertEquals(message.packedUint64, restored.packedUint64List.map { it.toULong() })
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Packed>(restored.toByteArray())
+ assertEquals(message, restoredMessage)
+ }
+}
diff --git a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3PrimitiveTest.kt b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3PrimitiveTest.kt
new file mode 100644
index 0000000..a7363f8
--- /dev/null
+++ b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3PrimitiveTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.protobuf.conformance
+
+import com.google.protobuf_test_messages.proto3.*
+import kotlinx.serialization.*
+import kotlinx.serialization.protobuf.*
+import kotlin.test.*
+
+@Serializable
+data class KTestMessagesProto3Primitive(
+ @ProtoNumber(1) val optionalInt32: Int = 0,
+ @ProtoNumber(2) val optionalInt64: Long = 0,
+ @ProtoNumber(3) val optionalUint32: UInt = 0U,
+ @ProtoNumber(4) val optionalUint64: ULong = 0UL,
+ @ProtoNumber(5) @ProtoType(ProtoIntegerType.SIGNED) val optionalSint32: Int = 0,
+ @ProtoNumber(6) @ProtoType(ProtoIntegerType.SIGNED) val optionalSint64: Long = 0,
+ @ProtoNumber(7) @ProtoType(ProtoIntegerType.FIXED) val optionalFixed32: Int = 0,
+ @ProtoNumber(8) @ProtoType(ProtoIntegerType.FIXED) val optionalFixed64: Long = 0,
+ @ProtoNumber(9) @ProtoType(ProtoIntegerType.FIXED) val optionalSfixed32: Int = 0,
+ @ProtoNumber(10) @ProtoType(ProtoIntegerType.FIXED) val optionalSfixed64: Long = 0,
+ @ProtoNumber(11) val optionalFloat: Float = 0.0f,
+ @ProtoNumber(12) val optionalDouble: Double = 0.0,
+ @ProtoNumber(13) val optionalBool: Boolean = false,
+ @ProtoNumber(14) val optionalString: String = "",
+ @ProtoNumber(15) val optionalBytes: ByteArray = byteArrayOf(),
+)
+
+class Proto3PrimitiveTest {
+ @Test
+ fun default() {
+ val message = KTestMessagesProto3Primitive(
+ optionalInt32 = Int.MAX_VALUE,
+ optionalInt64 = Long.MAX_VALUE,
+ optionalUint32 = UInt.MAX_VALUE,
+ optionalUint64 = ULong.MAX_VALUE,
+ optionalSint32 = Int.MAX_VALUE,
+ optionalSint64 = Long.MAX_VALUE,
+ optionalFixed32 = Int.MAX_VALUE,
+ optionalFixed64 = Long.MAX_VALUE,
+ optionalSfixed32 = Int.MAX_VALUE,
+ optionalSfixed64 = Long.MAX_VALUE,
+ optionalFloat = Float.MAX_VALUE,
+ optionalDouble = Double.MAX_VALUE,
+ optionalBool = true,
+ optionalString = "string",
+ optionalBytes = byteArrayOf(1, 2, 3, 4, 5)
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+ assertEquals(message.optionalInt32, restored.optionalInt32)
+ assertEquals(message.optionalInt64, restored.optionalInt64)
+ assertEquals(message.optionalUint32, restored.optionalUint32.toUInt())
+ assertEquals(message.optionalUint64, restored.optionalUint64.toULong())
+ assertEquals(message.optionalSint32, restored.optionalSint32)
+ assertEquals(message.optionalSint64, restored.optionalSint64)
+ assertEquals(message.optionalFixed32, restored.optionalFixed32)
+ assertEquals(message.optionalFixed64, restored.optionalFixed64)
+ assertEquals(message.optionalSfixed32, restored.optionalSfixed32)
+ assertEquals(message.optionalSfixed64, restored.optionalSfixed64)
+ assertEquals(message.optionalFloat, restored.optionalFloat)
+ assertEquals(message.optionalDouble, restored.optionalDouble)
+ assertEquals(message.optionalBool, restored.optionalBool)
+ assertEquals(message.optionalString, restored.optionalString)
+ assertContentEquals(message.optionalBytes, restored.optionalBytes.toByteArray())
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Primitive>(restored.toByteArray())
+
+ // [equals] method is not implemented for [ByteArray] so we need to compare it separately.
+ assertEquals(message, restoredMessage.copy(optionalBytes = message.optionalBytes))
+ assertContentEquals(message.optionalBytes, restoredMessage.optionalBytes)
+ }
+}
diff --git a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3RepeatedTest.kt b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3RepeatedTest.kt
new file mode 100644
index 0000000..b3dab8c
--- /dev/null
+++ b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3RepeatedTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.protobuf.conformance
+
+import com.google.protobuf_test_messages.proto3.*
+import io.kotlintest.properties.*
+import kotlinx.serialization.*
+import kotlinx.serialization.protobuf.*
+import kotlin.test.*
+
+@Serializable
+data class KTestMessagesProto3Repeated(
+ @ProtoNumber(31) @ProtoPacked val repeatedInt32: List<Int> = emptyList(),
+ @ProtoNumber(32) @ProtoPacked val repeatedInt64: List<Long> = emptyList(),
+ @ProtoNumber(33) @ProtoPacked val repeatedUint32: List<UInt> = emptyList(),
+ @ProtoNumber(34) @ProtoPacked val repeatedUint64: List<ULong> = emptyList(),
+ @ProtoNumber(35) @ProtoPacked val repeatedSint32: List<Int> = emptyList(),
+ @ProtoNumber(36) @ProtoPacked val repeatedSint64: List<Long> = emptyList(),
+ @ProtoNumber(37) @ProtoPacked val repeatedFixed32: List<Int> = emptyList(),
+ @ProtoNumber(38) @ProtoPacked val repeatedFixed64: List<Long> = emptyList(),
+ @ProtoNumber(39) @ProtoPacked val repeatedSfixed32: List<Int> = emptyList(),
+ @ProtoNumber(40) @ProtoPacked val repeatedSfixed64: List<Long> = emptyList(),
+ @ProtoNumber(41) @ProtoPacked val repeatedFloat: List<Float> = emptyList(),
+ @ProtoNumber(42) @ProtoPacked val repeatedDouble: List<Double> = emptyList(),
+ @ProtoNumber(43) @ProtoPacked val repeatedBool: List<Boolean> = emptyList(),
+ @ProtoNumber(44) val repeatedString: List<String> = emptyList(),
+ @ProtoNumber(45) val repeatedBytes: List<ByteArray> = emptyList(),
+ @ProtoNumber(48) val repeatedNestedMessages: List<KTestMessagesProto3Message.KNestedMessage> = emptyList(),
+ @ProtoNumber(49) val repeatedForeignMessages: List<KForeignMessage> = emptyList(),
+)
+
+class Proto3RepeatedTest {
+ @Test
+ fun default() {
+ val message = KTestMessagesProto3Repeated(
+ repeatedInt32 = Gen.list(Gen.int()).generate(),
+ repeatedInt64 = Gen.list(Gen.long()).generate(),
+ repeatedFloat = Gen.list(Gen.float()).generate(),
+ repeatedDouble = Gen.list(Gen.double()).generate(),
+ repeatedBool = Gen.list(Gen.bool()).generate(),
+ repeatedString = Gen.list(Gen.string()).generate(),
+ repeatedBytes = Gen.list(Gen.string().map { it.toByteArray() }).generate(),
+ repeatedNestedMessages = listOf(
+ KTestMessagesProto3Message.KNestedMessage(
+ 1,
+ null
+ ),
+ KTestMessagesProto3Message.KNestedMessage(
+ 2,
+ KTestMessagesProto3Message(
+ KTestMessagesProto3Message.KNestedMessage(3, null),
+ )
+ )
+ ),
+ repeatedForeignMessages = listOf(
+ KForeignMessage(1),
+ KForeignMessage(-12),
+ )
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+ assertEquals(message.repeatedInt32, restored.repeatedInt32List)
+ assertEquals(message.repeatedInt64, restored.repeatedInt64List)
+ assertEquals(message.repeatedFloat, restored.repeatedFloatList)
+ assertEquals(message.repeatedDouble, restored.repeatedDoubleList)
+ assertEquals(message.repeatedBool, restored.repeatedBoolList)
+ assertEquals(message.repeatedString, restored.repeatedStringList)
+ assertEquals(message.repeatedNestedMessages.map { it.toProto() }, restored.repeatedNestedMessageList)
+ assertEquals(message.repeatedForeignMessages.map { it.toProto() }, restored.repeatedForeignMessageList)
+ assertEquals(message.repeatedBytes.map { it.toList() }, restored.repeatedBytesList.map { it.toList() })
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Repeated>(restored.toByteArray())
+ // [equals] method is not implemented for [ByteArray] so we need to compare it separately.
+ assertEquals(message, restoredMessage.copy(repeatedBytes = message.repeatedBytes))
+ assertContentEquals(
+ message.repeatedBytes.flatMap { it.toList() },
+ restoredMessage.repeatedBytes.flatMap { it.toList() },
+ )
+ }
+
+ @Test
+ @Ignore
+ // Issue: https://github.com/Kotlin/kotlinx.serialization/issues/2419
+ fun signedAndFixed() {
+ val message = KTestMessagesProto3Repeated(
+ repeatedSint32 = Gen.list(Gen.int()).generate(),
+ repeatedSint64 = Gen.list(Gen.long()).generate(),
+ repeatedFixed32 = Gen.list(Gen.int()).generate(),
+ repeatedFixed64 = Gen.list(Gen.long()).generate(),
+ repeatedSfixed32 = Gen.list(Gen.int()).generate(),
+ repeatedSfixed64 = Gen.list(Gen.long()).generate(),
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+ assertEquals(message.repeatedUint32, restored.repeatedUint32List.map { it.toUInt() })
+ assertEquals(message.repeatedUint64, restored.repeatedUint64List.map { it.toULong() })
+ assertEquals(message.repeatedSint32, restored.repeatedSint32List)
+ assertEquals(message.repeatedSint64, restored.repeatedSint64List)
+ assertEquals(message.repeatedFixed32, restored.repeatedFixed32List)
+ assertEquals(message.repeatedFixed64, restored.repeatedFixed64List)
+ assertEquals(message.repeatedSfixed32, restored.repeatedSfixed32List)
+ assertEquals(message.repeatedSfixed64, restored.repeatedSfixed64List)
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Repeated>(restored.toByteArray())
+ assertEquals(message, restoredMessage)
+ }
+
+
+ @Test
+ @Ignore
+ // Issue: https://github.com/Kotlin/kotlinx.serialization/issues/2418
+ fun unsigned() {
+ val message = KTestMessagesProto3Repeated(
+ repeatedUint32 = Gen.list(Gen.int().map { it.toUInt() }).generate(),
+ repeatedUint64 = Gen.list(Gen.long().map { it.toULong() }).generate(),
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+ assertEquals(message.repeatedUint32, restored.repeatedUint32List.map { it.toUInt() })
+ assertEquals(message.repeatedUint64, restored.repeatedUint64List.map { it.toULong() })
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Repeated>(restored.toByteArray())
+ assertEquals(message, restoredMessage)
+ }
+}
diff --git a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3UnpackedTest.kt b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3UnpackedTest.kt
new file mode 100644
index 0000000..dad773d
--- /dev/null
+++ b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3UnpackedTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.protobuf.conformance
+
+import com.google.protobuf_test_messages.proto3.*
+import io.kotlintest.properties.*
+import kotlinx.serialization.*
+import kotlinx.serialization.protobuf.*
+import kotlin.test.*
+
+@Serializable
+data class KTestMessagesProto3Unpacked(
+ @ProtoNumber(89) val unpackedInt32: List<Int> = emptyList(),
+ @ProtoNumber(90) val unpackedInt64: List<Long> = emptyList(),
+ @ProtoNumber(91) val unpackedUint32: List<UInt> = emptyList(),
+ @ProtoNumber(92) val unpackedUint64: List<ULong> = emptyList(),
+ @ProtoNumber(93) val unpackedSint32: List<Int> = emptyList(),
+ @ProtoNumber(94) val unpackedSint64: List<Long> = emptyList(),
+ @ProtoNumber(95) val unpackedFixed32: List<Int> = emptyList(),
+ @ProtoNumber(96) val unpackedFixed64: List<Long> = emptyList(),
+ @ProtoNumber(97) val unpackedSfixed32: List<Int> = emptyList(),
+ @ProtoNumber(98) val unpackedSfixed64: List<Long> = emptyList(),
+ @ProtoNumber(99) val unpackedFloat: List<Float> = emptyList(),
+ @ProtoNumber(100) val unpackedDouble: List<Double> = emptyList(),
+ @ProtoNumber(101) val unpackedBool: List<Boolean> = emptyList(),
+)
+
+class Proto3UnpackedTest {
+ @Test
+ fun default() {
+ val message = KTestMessagesProto3Unpacked(
+ unpackedInt32 = Gen.list(Gen.int()).generate(),
+ unpackedInt64 = Gen.list(Gen.long()).generate(),
+ unpackedUint32 = Gen.list(Gen.int().map { it.toUInt() }).generate(),
+ unpackedUint64 = Gen.list(Gen.long().map { it.toULong() }).generate(),
+ unpackedFloat = Gen.list(Gen.float()).generate(),
+ unpackedDouble = Gen.list(Gen.double()).generate(),
+ unpackedBool = Gen.list(Gen.bool()).generate(),
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+ assertEquals(message.unpackedInt32, restored.unpackedInt32List)
+ assertEquals(message.unpackedInt64, restored.unpackedInt64List)
+ assertEquals(message.unpackedUint32, restored.unpackedUint32List.map { it.toUInt() })
+ assertEquals(message.unpackedUint64, restored.unpackedUint64List.map { it.toULong() })
+ assertEquals(message.unpackedFloat, restored.unpackedFloatList)
+ assertEquals(message.unpackedDouble, restored.unpackedDoubleList)
+ assertEquals(message.unpackedBool, restored.unpackedBoolList)
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Unpacked>(restored.toByteArray())
+ assertEquals(message, restoredMessage)
+ }
+
+ @Test
+ @Ignore
+ // Issue: https://github.com/Kotlin/kotlinx.serialization/issues/2419
+ fun signedAndFixed() {
+ val message = KTestMessagesProto3Unpacked(
+ unpackedSint32 = Gen.list(Gen.int()).generate(),
+ unpackedSint64 = Gen.list(Gen.long()).generate(),
+ unpackedFixed32 = Gen.list(Gen.int()).generate(),
+ unpackedFixed64 = Gen.list(Gen.long()).generate(),
+ unpackedSfixed32 = Gen.list(Gen.int()).generate(),
+ unpackedSfixed64 = Gen.list(Gen.long()).generate(),
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+ assertEquals(message.unpackedSint32, restored.unpackedSint32List)
+ assertEquals(message.unpackedSint64, restored.unpackedSint64List)
+ assertEquals(message.unpackedFixed32, restored.unpackedFixed32List)
+ assertEquals(message.unpackedFixed64, restored.unpackedFixed64List)
+ assertEquals(message.unpackedSfixed32, restored.unpackedSfixed32List)
+ assertEquals(message.unpackedSfixed64, restored.unpackedSfixed64List)
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Unpacked>(restored.toByteArray())
+ assertEquals(message, restoredMessage)
+ }
+}
diff --git a/formats/protobuf/testProto/test_messages_proto3.proto b/formats/protobuf/testProto/test_messages_proto3.proto
new file mode 100644
index 0000000..6b27995
--- /dev/null
+++ b/formats/protobuf/testProto/test_messages_proto3.proto
@@ -0,0 +1,289 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Test schema for proto3 messages. This test schema is used by:
+//
+// - benchmarks
+// - fuzz tests
+// - conformance tests
+//
+// https://github.com/protocolbuffers/protobuf/blob/5e03386555544e39c21236dca0097123edec8769/src/google/protobuf/test_messages_proto3.proto
+
+syntax = "proto3";
+
+package protobuf_test_messages.proto3;
+
+option java_package = "com.google.protobuf_test_messages.proto3";
+option objc_class_prefix = "Proto3";
+
+// This is the default, but we specify it here explicitly.
+option optimize_for = SPEED;
+
+import "google/protobuf/any.proto";
+import "google/protobuf/duration.proto";
+import "google/protobuf/field_mask.proto";
+import "google/protobuf/struct.proto";
+import "google/protobuf/timestamp.proto";
+import "google/protobuf/wrappers.proto";
+
+option cc_enable_arenas = true;
+
+// This proto includes every type of field in both singular and repeated
+// forms.
+//
+// Also, crucially, all messages and enums in this file are eventually
+// submessages of this message. So for example, a fuzz test of TestAllTypes
+// could trigger bugs that occur in any message type in this file. We verify
+// this stays true in a unit test.
+message TestAllTypesProto3 {
+ message NestedMessage {
+ int32 a = 1;
+ TestAllTypesProto3 corecursive = 2;
+ }
+
+ enum NestedEnum {
+ FOO = 0;
+ BAR = 1;
+ BAZ = 2;
+ NEG = -1; // Intentionally negative.
+ }
+
+ enum AliasedEnum {
+ option allow_alias = true;
+
+ ALIAS_FOO = 0;
+ ALIAS_BAR = 1;
+ ALIAS_BAZ = 2;
+ MOO = 2;
+ moo = 2;
+ bAz = 2;
+ }
+
+ // Singular
+ int32 optional_int32 = 1;
+ int64 optional_int64 = 2;
+ uint32 optional_uint32 = 3;
+ uint64 optional_uint64 = 4;
+ sint32 optional_sint32 = 5;
+ sint64 optional_sint64 = 6;
+ fixed32 optional_fixed32 = 7;
+ fixed64 optional_fixed64 = 8;
+ sfixed32 optional_sfixed32 = 9;
+ sfixed64 optional_sfixed64 = 10;
+ float optional_float = 11;
+ double optional_double = 12;
+ bool optional_bool = 13;
+ string optional_string = 14;
+ bytes optional_bytes = 15;
+
+ NestedMessage optional_nested_message = 18;
+ ForeignMessage optional_foreign_message = 19;
+
+ NestedEnum optional_nested_enum = 21;
+ ForeignEnum optional_foreign_enum = 22;
+ AliasedEnum optional_aliased_enum = 23;
+
+ string optional_string_piece = 24 [ctype = STRING_PIECE];
+ string optional_cord = 25 [ctype = CORD];
+
+ TestAllTypesProto3 recursive_message = 27;
+
+ // Repeated
+ repeated int32 repeated_int32 = 31;
+ repeated int64 repeated_int64 = 32;
+ repeated uint32 repeated_uint32 = 33;
+ repeated uint64 repeated_uint64 = 34;
+ repeated sint32 repeated_sint32 = 35;
+ repeated sint64 repeated_sint64 = 36;
+ repeated fixed32 repeated_fixed32 = 37;
+ repeated fixed64 repeated_fixed64 = 38;
+ repeated sfixed32 repeated_sfixed32 = 39;
+ repeated sfixed64 repeated_sfixed64 = 40;
+ repeated float repeated_float = 41;
+ repeated double repeated_double = 42;
+ repeated bool repeated_bool = 43;
+ repeated string repeated_string = 44;
+ repeated bytes repeated_bytes = 45;
+
+ repeated NestedMessage repeated_nested_message = 48;
+ repeated ForeignMessage repeated_foreign_message = 49;
+
+ repeated NestedEnum repeated_nested_enum = 51;
+ repeated ForeignEnum repeated_foreign_enum = 52;
+
+ repeated string repeated_string_piece = 54 [ctype = STRING_PIECE];
+ repeated string repeated_cord = 55 [ctype = CORD];
+
+ // Packed
+ repeated int32 packed_int32 = 75 [packed = true];
+ repeated int64 packed_int64 = 76 [packed = true];
+ repeated uint32 packed_uint32 = 77 [packed = true];
+ repeated uint64 packed_uint64 = 78 [packed = true];
+ repeated sint32 packed_sint32 = 79 [packed = true];
+ repeated sint64 packed_sint64 = 80 [packed = true];
+ repeated fixed32 packed_fixed32 = 81 [packed = true];
+ repeated fixed64 packed_fixed64 = 82 [packed = true];
+ repeated sfixed32 packed_sfixed32 = 83 [packed = true];
+ repeated sfixed64 packed_sfixed64 = 84 [packed = true];
+ repeated float packed_float = 85 [packed = true];
+ repeated double packed_double = 86 [packed = true];
+ repeated bool packed_bool = 87 [packed = true];
+ repeated NestedEnum packed_nested_enum = 88 [packed = true];
+
+ // Unpacked
+ repeated int32 unpacked_int32 = 89 [packed = false];
+ repeated int64 unpacked_int64 = 90 [packed = false];
+ repeated uint32 unpacked_uint32 = 91 [packed = false];
+ repeated uint64 unpacked_uint64 = 92 [packed = false];
+ repeated sint32 unpacked_sint32 = 93 [packed = false];
+ repeated sint64 unpacked_sint64 = 94 [packed = false];
+ repeated fixed32 unpacked_fixed32 = 95 [packed = false];
+ repeated fixed64 unpacked_fixed64 = 96 [packed = false];
+ repeated sfixed32 unpacked_sfixed32 = 97 [packed = false];
+ repeated sfixed64 unpacked_sfixed64 = 98 [packed = false];
+ repeated float unpacked_float = 99 [packed = false];
+ repeated double unpacked_double = 100 [packed = false];
+ repeated bool unpacked_bool = 101 [packed = false];
+ repeated NestedEnum unpacked_nested_enum = 102 [packed = false];
+
+ // Map
+ map<int32, int32> map_int32_int32 = 56;
+ map<int64, int64> map_int64_int64 = 57;
+ map<uint32, uint32> map_uint32_uint32 = 58;
+ map<uint64, uint64> map_uint64_uint64 = 59;
+ map<sint32, sint32> map_sint32_sint32 = 60;
+ map<sint64, sint64> map_sint64_sint64 = 61;
+ map<fixed32, fixed32> map_fixed32_fixed32 = 62;
+ map<fixed64, fixed64> map_fixed64_fixed64 = 63;
+ map<sfixed32, sfixed32> map_sfixed32_sfixed32 = 64;
+ map<sfixed64, sfixed64> map_sfixed64_sfixed64 = 65;
+ map<int32, float> map_int32_float = 66;
+ map<int32, double> map_int32_double = 67;
+ map<bool, bool> map_bool_bool = 68;
+ map<string, string> map_string_string = 69;
+ map<string, bytes> map_string_bytes = 70;
+ map<string, NestedMessage> map_string_nested_message = 71;
+ map<string, ForeignMessage> map_string_foreign_message = 72;
+ map<string, NestedEnum> map_string_nested_enum = 73;
+ map<string, ForeignEnum> map_string_foreign_enum = 74;
+
+ oneof oneof_field {
+ uint32 oneof_uint32 = 111;
+ NestedMessage oneof_nested_message = 112;
+ string oneof_string = 113;
+ bytes oneof_bytes = 114;
+ bool oneof_bool = 115;
+ uint64 oneof_uint64 = 116;
+ float oneof_float = 117;
+ double oneof_double = 118;
+ NestedEnum oneof_enum = 119;
+ google.protobuf.NullValue oneof_null_value = 120;
+ }
+
+ // Well-known types
+ google.protobuf.BoolValue optional_bool_wrapper = 201;
+ google.protobuf.Int32Value optional_int32_wrapper = 202;
+ google.protobuf.Int64Value optional_int64_wrapper = 203;
+ google.protobuf.UInt32Value optional_uint32_wrapper = 204;
+ google.protobuf.UInt64Value optional_uint64_wrapper = 205;
+ google.protobuf.FloatValue optional_float_wrapper = 206;
+ google.protobuf.DoubleValue optional_double_wrapper = 207;
+ google.protobuf.StringValue optional_string_wrapper = 208;
+ google.protobuf.BytesValue optional_bytes_wrapper = 209;
+
+ repeated google.protobuf.BoolValue repeated_bool_wrapper = 211;
+ repeated google.protobuf.Int32Value repeated_int32_wrapper = 212;
+ repeated google.protobuf.Int64Value repeated_int64_wrapper = 213;
+ repeated google.protobuf.UInt32Value repeated_uint32_wrapper = 214;
+ repeated google.protobuf.UInt64Value repeated_uint64_wrapper = 215;
+ repeated google.protobuf.FloatValue repeated_float_wrapper = 216;
+ repeated google.protobuf.DoubleValue repeated_double_wrapper = 217;
+ repeated google.protobuf.StringValue repeated_string_wrapper = 218;
+ repeated google.protobuf.BytesValue repeated_bytes_wrapper = 219;
+
+ google.protobuf.Duration optional_duration = 301;
+ google.protobuf.Timestamp optional_timestamp = 302;
+ google.protobuf.FieldMask optional_field_mask = 303;
+ google.protobuf.Struct optional_struct = 304;
+ google.protobuf.Any optional_any = 305;
+ google.protobuf.Value optional_value = 306;
+ google.protobuf.NullValue optional_null_value = 307;
+
+ repeated google.protobuf.Duration repeated_duration = 311;
+ repeated google.protobuf.Timestamp repeated_timestamp = 312;
+ repeated google.protobuf.FieldMask repeated_fieldmask = 313;
+ repeated google.protobuf.Struct repeated_struct = 324;
+ repeated google.protobuf.Any repeated_any = 315;
+ repeated google.protobuf.Value repeated_value = 316;
+ repeated google.protobuf.ListValue repeated_list_value = 317;
+
+ // Test field-name-to-JSON-name convention.
+ // (protobuf says names can be any valid C/C++ identifier.)
+ int32 fieldname1 = 401;
+ int32 field_name2 = 402;
+ int32 _field_name3 = 403;
+ int32 field__name4_ = 404;
+ int32 field0name5 = 405;
+ int32 field_0_name6 = 406;
+ int32 fieldName7 = 407;
+ int32 FieldName8 = 408;
+ int32 field_Name9 = 409;
+ int32 Field_Name10 = 410;
+ int32 FIELD_NAME11 = 411;
+ int32 FIELD_name12 = 412;
+ int32 __field_name13 = 413;
+ int32 __Field_name14 = 414;
+ int32 field__name15 = 415;
+ int32 field__Name16 = 416;
+ int32 field_name17__ = 417;
+ int32 Field_name18__ = 418;
+
+ // Reserved for testing unknown fields
+ reserved 501 to 510;
+}
+
+message ForeignMessage {
+ int32 c = 1;
+}
+
+enum ForeignEnum {
+ FOREIGN_FOO = 0;
+ FOREIGN_BAR = 1;
+ FOREIGN_BAZ = 2;
+}
+
+message NullHypothesisProto3 {}
+
+message EnumOnlyProto3 {
+ enum Bool {
+ kFalse = 0;
+ kTrue = 1;
+ }
+}
diff --git a/formats/protobuf/wasmMain/src/kotlinx/serialization/protobuf/internal/Bytes.kt b/formats/protobuf/wasmMain/src/kotlinx/serialization/protobuf/internal/Bytes.kt
new file mode 100644
index 0000000..72fbfd0
--- /dev/null
+++ b/formats/protobuf/wasmMain/src/kotlinx/serialization/protobuf/internal/Bytes.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.protobuf.internal
+
+private fun Short.reverseBytes(): Short = (((this.toInt() and 0xff) shl 8) or ((this.toInt() and 0xffff) ushr 8)).toShort()
+
+internal actual fun Int.reverseBytes(): Int =
+ ((this and 0xffff).toShort().reverseBytes().toInt() shl 16) or ((this ushr 16).toShort().reverseBytes().toInt() and 0xffff)
+
+internal actual fun Long.reverseBytes(): Long =
+ ((this and 0xffffffff).toInt().reverseBytes().toLong() shl 32) or ((this ushr 32).toInt()
+ .reverseBytes().toLong() and 0xffffffff)
diff --git a/formats/protobuf/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt b/formats/protobuf/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt
new file mode 100644
index 0000000..fd359b7
--- /dev/null
+++ b/formats/protobuf/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt
@@ -0,0 +1,7 @@
+/*
+ * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.test
+
+public actual val currentPlatform: Platform = Platform.WASM
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index 63aedbe..bab6b61 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -5,7 +5,7 @@
group=org.jetbrains.kotlinx
version=1.6.1-SNAPSHOT
-kotlin.version=1.9.0
+kotlin.version=1.9.20-RC
# This version takes precedence if 'bootstrap' property passed to project
kotlin.version.snapshot=1.9.255-SNAPSHOT
@@ -20,7 +20,7 @@
# Only for tests
coroutines_version=1.6.4
kover_version=0.4.2
-okio_version=3.1.0
+okio_version=3.6.0
kover.enabled=true
diff --git a/gradle/configure-source-sets.gradle b/gradle/configure-source-sets.gradle
index 56a2239..532e2ca 100644
--- a/gradle/configure-source-sets.gradle
+++ b/gradle/configure-source-sets.gradle
@@ -2,6 +2,8 @@
* Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+import static KotlinVersion.*
+
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(11))
@@ -40,6 +42,10 @@
}
}
+ wasmJs {
+ d8()
+ }
+
sourceSets.all {
kotlin.srcDirs = ["$it.name/src"]
resources.srcDirs = ["$it.name/resources"]
@@ -90,6 +96,25 @@
}
}
+
+ wasmJsMain {
+ kotlin {
+ srcDir 'wasmMain/src'
+ }
+ dependencies {
+ api 'org.jetbrains.kotlin:kotlin-stdlib-wasm-js'
+ }
+ }
+
+ wasmJsTest {
+ kotlin {
+ srcDir 'wasmTest/src'
+ }
+ dependencies {
+ api 'org.jetbrains.kotlin:kotlin-test-wasm-js'
+ }
+ }
+
nativeMain.dependencies {
}
}
@@ -112,6 +137,7 @@
languageVersion = rootProject.ext.kotlin_lv_override
freeCompilerArgs += "-Xsuppress-version-warnings"
}
+ freeCompilerArgs += "-Xexpect-actual-classes"
}
}
compilations.main {
@@ -121,8 +147,7 @@
}
}
- // TODO: Remove deprecated linuxArm32Hfp and mingwX86 targets in 1.9.20.
- def targetsWithoutTestRunners = ["linuxArm32Hfp", "linuxArm64", "mingwX86"]
+ def targetsWithoutTestRunners = ["linuxArm64"]
configure(targets) {
// Configure additional binaries to run tests in the background
if (["macos", "linux", "mingw"].any { name.startsWith(it) && !targetsWithoutTestRunners.contains(name) }) {
diff --git a/gradle/native-targets.gradle b/gradle/native-targets.gradle
index 880b4b6..5b7f8a4 100644
--- a/gradle/native-targets.gradle
+++ b/gradle/native-targets.gradle
@@ -2,178 +2,41 @@
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-/**
- * Specifies what subset of Native targets to build
- *
- * ALL — all possible for compilation
- * HOST — host-specific ones, without cross compilation (e.g. do not build linuxX64 on macOS host)
- * SINGLE — only for current OS (to import into IDEA / quickly run tests)
- * DISABLED — disable all Native targets (useful with Kotlin compiler built from sources)
- *
- * For HOST mode, all targets are still listed in .module file, so HOST mode is useful for release process.
- */
-enum NativeState {
- ALL, HOST, SINGLE, DISABLED
-}
-
-def getNativeState(String description) {
- if (description == null) return NativeState.SINGLE
- switch (description.toLowerCase()) {
- case 'all':
- case 'true':
- return NativeState.ALL
- case 'host':
- return NativeState.HOST
- case 'disabled':
- return NativeState.DISABLED
- // 'single', 'false', etc
- default:
- return NativeState.SINGLE
- }
-}
-
-project.ext.ideaActive = System.getProperty('idea.active') == 'true'
-project.ext.nativeState = getNativeState(property('native.deploy'))
-project.ext.singleTargetMode = project.ext.ideaActive || (project.ext.nativeState == NativeState.SINGLE)
-
-project.ext.nativeMainSets = []
-project.ext.nativeTestSets = []
-
-/**
- * Disables compilation but leaves the target in .module file
- */
-def disableCompilation(targets) {
- configure(targets) {
- compilations.all {
- cinterops.all { project.tasks[interopProcessingTaskName].enabled = false }
- compileKotlinTask.enabled = false
- }
- binaries.all { linkTask.enabled = false }
-
- mavenPublication { publicationToDisable ->
- tasks.withType(AbstractPublishToMaven).all {
- onlyIf { publication != publicationToDisable }
- }
- tasks.withType(GenerateModuleMetadata).all {
- onlyIf { publication.get() != publicationToDisable }
- }
- }
- }
-}
-
-def getHostName() {
- def target = System.getProperty("os.name")
- if (target == 'Linux') return 'linux'
- if (target.startsWith('Windows')) return 'windows'
- if (target.startsWith('Mac')) return 'macos'
- return 'unknown'
-}
-
static def doesNotDependOnOkio(project) {
return !project.name.contains("json-okio") && !project.name.contains("json-tests")
}
kotlin {
- targets {
- def manager = project.ext.hostManager
- def linuxEnabled = manager.isEnabled(presets.linuxX64.konanTarget)
- def macosEnabled = manager.isEnabled(presets.macosX64.konanTarget)
- def winEnabled = manager.isEnabled(presets.mingwX64.konanTarget)
+ applyDefaultHierarchyTemplate {
- def ideaPreset = presets.linuxX64
- if (macosEnabled) ideaPreset = presets.macosX64
- if (winEnabled) ideaPreset = presets.mingwX64
- project.ext.ideaPreset = ideaPreset
- }
+ // According to https://kotlinlang.org/docs/native-target-support.html
+ // Tier 1
+ linuxX64()
+ macosX64()
+ macosArm64()
+ iosSimulatorArm64()
+ iosX64()
- targets {
- delegate.metaClass.addTarget = { preset ->
- def target = delegate.fromPreset(preset, preset.name)
- project.ext.nativeMainSets.add(target.compilations['main'].kotlinSourceSets.first())
- project.ext.nativeTestSets.add(target.compilations['test'].kotlinSourceSets.first())
- }
- }
+ // Tier 2
+ watchosSimulatorArm64()
+ watchosX64()
+ watchosArm32()
+ watchosArm64()
+ tvosSimulatorArm64()
+ tvosX64()
+ tvosArm64()
+ iosArm64()
+ linuxArm64()
- targets {
- if (project.ext.nativeState == NativeState.DISABLED) return
-
- String[] versionComponents = (~/[.-]/).split(compilerVersion, 4)
- String[] versionComponents1920 = ["1", "9", "20"]
- def isAtLeast1920 = Arrays.compare(versionComponents, versionComponents1920) { lhs, rhs ->
- (lhs as int) <=> (rhs as int)
- } >= 0
-
- if (project.ext.singleTargetMode) {
- fromPreset(project.ext.ideaPreset, 'native')
- } else {
- // According to https://kotlinlang.org/docs/native-target-support.html
- // Tier 1
- addTarget(presets.linuxX64)
- addTarget(presets.macosX64)
- addTarget(presets.macosArm64)
- addTarget(presets.iosSimulatorArm64)
- addTarget(presets.iosX64)
-
- // Tier 2
- if (doesNotDependOnOkio(project)) {
- addTarget(presets.linuxArm64)
- }
- addTarget(presets.watchosSimulatorArm64)
- addTarget(presets.watchosX64)
- addTarget(presets.watchosArm32)
- addTarget(presets.watchosArm64)
- addTarget(presets.tvosSimulatorArm64)
- addTarget(presets.tvosX64)
- addTarget(presets.tvosArm64)
- addTarget(presets.iosArm64)
-
- // Tier 3
- if (doesNotDependOnOkio(project)) {
- addTarget(presets.androidNativeArm32)
- addTarget(presets.androidNativeArm64)
- addTarget(presets.androidNativeX86)
- addTarget(presets.androidNativeX64)
- addTarget(presets.watchosDeviceArm64)
- }
-
- addTarget(presets.mingwX64)
-
- // Deprecated, but were provided by kotlinx.serialization; can be removed only in 1.9.20 release.
- if (!isAtLeast1920) {
- addTarget(presets.watchosX86)
-
- if (doesNotDependOnOkio(project)) {
- addTarget(presets.iosArm32)
- addTarget(presets.linuxArm32Hfp)
- addTarget(presets.mingwX86)
- }
- }
-
- }
-
- if (project.ext.nativeState == NativeState.HOST) {
- // linux targets that can cross-compile on all three hosts
- def linuxCrossCompileTargets = isAtLeast1920 ? [presets.linuxX64] : [presets.linuxX64, presets.linuxArm32Hfp, presets.linuxArm64]
- if (getHostName() != "linux") {
- disableCompilation(linuxCrossCompileTargets)
- }
- }
- }
-
-
- sourceSets {
- nativeMain { dependsOn commonMain }
- // Empty source set is required in order to have native tests task
- nativeTest { dependsOn commonTest }
-
- if (!project.ext.singleTargetMode) {
- configure(project.ext.nativeMainSets) {
- dependsOn nativeMain
- }
-
- configure(project.ext.nativeTestSets) {
- dependsOn nativeTest
- }
+ // Tier 3
+ mingwX64()
+ // https://github.com/square/okio/issues/1242#issuecomment-1759357336
+ if (doesNotDependOnOkio(project)) {
+ androidNativeArm32()
+ androidNativeArm64()
+ androidNativeX86()
+ androidNativeX64()
+ watchosDeviceArm64()
}
}
}
diff --git a/gradle/teamcity.gradle b/gradle/teamcity.gradle
index ceb8a81..950494d 100644
--- a/gradle/teamcity.gradle
+++ b/gradle/teamcity.gradle
@@ -3,7 +3,7 @@
*/
def teamcitySuffix = project.findProperty("teamcitySuffix")?.toString()
-if (project.hasProperty("teamcity") && !(build_snapshot_train || rootProject.properties['build_snapshot_up'])) {
+if (!teamcityInteractionDisabled && project.hasProperty("teamcity") && !(build_snapshot_train || rootProject.properties['build_snapshot_up'])) {
// Tell teamcity about version number
def postfix = (teamcitySuffix == null) ? "" : " ($teamcitySuffix)"
println("##teamcity[buildNumber '${project.version}${postfix}']")
diff --git a/guide/test/PolymorphismTest.kt b/guide/test/PolymorphismTest.kt
index f614763..344ed24 100644
--- a/guide/test/PolymorphismTest.kt
+++ b/guide/test/PolymorphismTest.kt
@@ -23,9 +23,9 @@
@Test
fun testExamplePoly03() {
captureOutput("ExamplePoly03") { example.examplePoly03.main() }.verifyOutputLinesStart(
- "Exception in thread \"main\" kotlinx.serialization.SerializationException: Class 'OwnedProject' is not registered for polymorphic serialization in the scope of 'Project'.",
- "To be registered automatically, class 'OwnedProject' has to be '@Serializable', and the base class 'Project' has to be sealed and '@Serializable'.",
- "Alternatively, register the serializer for 'OwnedProject' explicitly in a corresponding SerializersModule."
+ "Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for subclass 'OwnedProject' is not found in the polymorphic scope of 'Project'.",
+ "Check if class with serial name 'OwnedProject' exists and serializer is registered in a corresponding SerializersModule.",
+ "To be registered automatically, class 'OwnedProject' has to be '@Serializable', and the base class 'Project' has to be sealed and '@Serializable'."
)
}
@@ -133,7 +133,8 @@
@Test
fun testExamplePoly18() {
captureOutput("ExamplePoly18") { example.examplePoly18.main() }.verifyOutputLinesStart(
- "Exception in thread \"main\" kotlinx.serialization.json.internal.JsonDecodingException: Polymorphic serializer was not found for class discriminator 'unknown'"
+ "Exception in thread \"main\" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 0: Serializer for subclass 'unknown' is not found in the polymorphic scope of 'Project' at path: $",
+ "Check if class with serial name 'unknown' exists and serializer is registered in a corresponding SerializersModule."
)
}
diff --git a/integration-test/build.gradle b/integration-test/build.gradle
index dda6834..225181b 100644
--- a/integration-test/build.gradle
+++ b/integration-test/build.gradle
@@ -41,15 +41,17 @@
}
}
}
+ wasmJs {
+ d8()
+ }
jvm {
withJava()
}
- // For ARM, should be changed to iosArm32 or iosArm64
- // For Linux, should be changed to e.g. linuxX64
- // For MacOS, should be changed to e.g. macosX64
- // For Windows, should be changed to e.g. mingwX64
- macosX64("macos")
- linuxX64("linux")
+ macosX64()
+ macosArm64()
+ linuxX64()
+ mingwX64()
+
sourceSets {
all {
languageSettings {
@@ -95,16 +97,29 @@
implementation kotlin('test-js')
}
}
- macosMain {
+ wasmJsMain {
dependencies {
+ api 'org.jetbrains.kotlin:kotlin-stdlib-wasm-js'
}
}
- macosTest {}
- linuxMain {
- kotlin.srcDirs = ["src/macosMain/kotlin"]
+
+ wasmJsTest {
+ dependencies {
+ api 'org.jetbrains.kotlin:kotlin-test-wasm-js'
+ }
}
- linuxTest {
- kotlin.srcDirs = ["src/macosTest/kotlin"]
+ }
+
+ targets.all {
+ compilations.all {
+ kotlinOptions {
+ freeCompilerArgs += "-Xexpect-actual-classes"
+ }
+ }
+ compilations.main {
+ kotlinOptions {
+ allWarningsAsErrors = true
+ }
}
}
}
diff --git a/integration-test/gradle.properties b/integration-test/gradle.properties
index e0e8855..a0c9623 100644
--- a/integration-test/gradle.properties
+++ b/integration-test/gradle.properties
@@ -2,7 +2,7 @@
# Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
#
-mainKotlinVersion=1.9.0
+mainKotlinVersion=1.9.20-RC
mainLibVersion=1.6.1-SNAPSHOT
kotlin.code.style=official
diff --git a/integration-test/src/commonTest/kotlin/sample/JsonTest.kt b/integration-test/src/commonTest/kotlin/sample/JsonTest.kt
index 6b70435..88a7a0d 100644
--- a/integration-test/src/commonTest/kotlin/sample/JsonTest.kt
+++ b/integration-test/src/commonTest/kotlin/sample/JsonTest.kt
@@ -12,7 +12,7 @@
import kotlin.reflect.*
import kotlin.test.*
-public val jsonWithDefaults = Json { encodeDefaults = true }
+val jsonWithDefaults = Json { encodeDefaults = true }
class JsonTest {
@@ -129,10 +129,9 @@
assertEquals("""Derived2(state1='foo')""", restored2.toString())
}
- @Suppress("NAME_SHADOWING")
private fun checkNotRegisteredMessage(exception: SerializationException) {
val expectedText =
- "is not registered for polymorphic serialization in the scope of"
+ "is not found in the polymorphic scope of"
assertEquals(true, exception.message?.contains(expectedText))
}
diff --git a/integration-test/src/macosMain/kotlin/sample/SampleMacos.kt b/integration-test/src/nativeMain/kotlin/sample/SampleMacos.kt
similarity index 100%
rename from integration-test/src/macosMain/kotlin/sample/SampleMacos.kt
rename to integration-test/src/nativeMain/kotlin/sample/SampleMacos.kt
diff --git a/integration-test/src/macosTest/kotlin/sample/SampleTestsNative.kt b/integration-test/src/nativeTest/kotlin/sample/SampleTestsNative.kt
similarity index 99%
rename from integration-test/src/macosTest/kotlin/sample/SampleTestsNative.kt
rename to integration-test/src/nativeTest/kotlin/sample/SampleTestsNative.kt
index 5ea6727..f86bf96 100644
--- a/integration-test/src/macosTest/kotlin/sample/SampleTestsNative.kt
+++ b/integration-test/src/nativeTest/kotlin/sample/SampleTestsNative.kt
@@ -8,4 +8,4 @@
fun testHello() {
assertTrue("Native" in hello())
}
-}
\ No newline at end of file
+}
diff --git a/integration-test/src/wasmJsMain/kotlin/sample/SampleWasm.kt b/integration-test/src/wasmJsMain/kotlin/sample/SampleWasm.kt
new file mode 100644
index 0000000..6319036
--- /dev/null
+++ b/integration-test/src/wasmJsMain/kotlin/sample/SampleWasm.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2019 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package sample
+
+actual object Platform {
+ actual val name: String = "Wasm"
+}
diff --git a/integration-test/src/macosTest/kotlin/sample/SampleTestsNative.kt b/integration-test/src/wasmJsTest/kotlin/sample/SampleTestsWasm.kt
similarity index 62%
copy from integration-test/src/macosTest/kotlin/sample/SampleTestsNative.kt
copy to integration-test/src/wasmJsTest/kotlin/sample/SampleTestsWasm.kt
index 5ea6727..82dfb78 100644
--- a/integration-test/src/macosTest/kotlin/sample/SampleTestsNative.kt
+++ b/integration-test/src/wasmJsTest/kotlin/sample/SampleTestsWasm.kt
@@ -3,9 +3,9 @@
import kotlin.test.Test
import kotlin.test.assertTrue
-class SampleTestsNative {
+class SampleTestsWasm {
@Test
fun testHello() {
- assertTrue("Native" in hello())
+ assertTrue("Wasm" in hello())
}
-}
\ No newline at end of file
+}