diff --git a/CHANGELOG.md b/CHANGELOG.md index 216d595784..19934a5032 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +1.9.0 / 2025-06-27 +================== + +This release updates Kotlin version to 2.2.0, includes several bugfixes and provides serializers for kotlin.time.Instant. + +## Add kotlin.time.Instant serializers + +Instant class was moved from kotlinx-datetime library to Kotlin standard library. +As a result, kotlinx-datetime 0.7.0 no longer has serializers for the Instant class. +To use new kotlin.time.Instant class in your @Serializable classes, +you can use this 1.9.0 kotlinx-serialization version (Kotlin 2.2 is required). +You can choose between default `InstantSerializer` which uses its string representation, +or specify `InstantComponentSerializer` that represents instant as its components. +See details in the [PR](https://github.com/Kotlin/kotlinx.serialization/pull/2945). + +## Other bugfixes + * Fix resize in JsonPath (#2995) + * Fixed proguard rules for obfuscation to work correctly (#2983) + 1.8.1 / 2025-03-31 ================== diff --git a/README.md b/README.md index febcb102bf..2da3b067cc 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ [![JetBrains official project](https://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) [![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0) [![TeamCity build](https://img.shields.io/teamcity/http/teamcity.jetbrains.com/s/KotlinTools_KotlinxSerialization_Ko.svg)](https://teamcity.jetbrains.com/viewType.html?buildTypeId=KotlinTools_KotlinxSerialization_Ko&guest=1) -[![Kotlin](https://img.shields.io/badge/kotlin-2.1.20-blue.svg?logo=kotlin)](http://kotlinlang.org) -[![Maven Central](https://img.shields.io/maven-central/v/org.jetbrains.kotlinx/kotlinx-serialization-core/1.8.1)](https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-serialization-core/1.8.1) +[![Kotlin](https://img.shields.io/badge/kotlin-2.2.0-blue.svg?logo=kotlin)](http://kotlinlang.org) +[![Maven Central](https://img.shields.io/maven-central/v/org.jetbrains.kotlinx/kotlinx-serialization-core/1.9.0)](https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-serialization-core/1.9.0) [![KDoc link](https://img.shields.io/badge/API_reference-KDoc-blue)](https://kotlinlang.org/api/kotlinx.serialization/) [![Slack channel](https://img.shields.io/badge/chat-slack-blue.svg?logo=slack)](https://kotlinlang.slack.com/messages/serialization/) @@ -94,8 +94,8 @@ Kotlin DSL: ```kotlin plugins { - kotlin("jvm") version "2.1.20" // or kotlin("multiplatform") or any other kotlin plugin - kotlin("plugin.serialization") version "2.1.20" + kotlin("jvm") version "2.2.0" // or kotlin("multiplatform") or any other kotlin plugin + kotlin("plugin.serialization") version "2.2.0" } ``` @@ -103,8 +103,8 @@ Groovy DSL: ```gradle plugins { - id 'org.jetbrains.kotlin.multiplatform' version '2.1.20' - id 'org.jetbrains.kotlin.plugin.serialization' version '2.1.20' + id 'org.jetbrains.kotlin.multiplatform' version '2.2.0' + id 'org.jetbrains.kotlin.plugin.serialization' version '2.2.0' } ``` @@ -122,7 +122,7 @@ buildscript { repositories { mavenCentral() } dependencies { - val kotlinVersion = "2.1.20" + val kotlinVersion = "2.2.0" classpath(kotlin("gradle-plugin", version = kotlinVersion)) classpath(kotlin("serialization", version = kotlinVersion)) } @@ -133,7 +133,7 @@ Groovy DSL: ```gradle buildscript { - ext.kotlin_version = '2.1.20' + ext.kotlin_version = '2.2.0' repositories { mavenCentral() } dependencies { @@ -163,7 +163,7 @@ repositories { } dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") } ``` @@ -175,7 +175,7 @@ repositories { } dependencies { - implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1" + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0" } ``` @@ -265,8 +265,8 @@ Ensure the proper version of Kotlin and serialization version: ```xml - 2.1.20 - 1.8.1 + 2.2.0 + 1.9.0 ``` diff --git a/build.gradle.kts b/build.gradle.kts index f2cfd85363..678d1cd829 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,13 +21,7 @@ plugins { repositories { mavenCentral() - maven("https://maven.pkg.jetbrains.space/kotlin/p/dokka/dev") - // kotlin-dev with space redirector - maven("https://cache-redirector.jetbrains.com/maven.pkg.jetbrains.space/kotlin/p/kotlin/dev") - // For Dokka that depends on kotlinx-html - maven("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven") - // For local development - mavenLocal() + maven("https://redirector.kotlinlang.org/maven/dev") } // == common projects settings setup @@ -59,7 +53,7 @@ allprojects { } repositories { mavenCentral() - maven("https://cache-redirector.jetbrains.com/maven.pkg.jetbrains.space/kotlin/p/kotlin/dev") + maven("https://redirector.kotlinlang.org/maven/dev") } } @@ -76,12 +70,13 @@ apiValidation { knit { siteRoot = "https://kotlinlang.org/api/kotlinx.serialization" - moduleDocs = "build/dokka/htmlMultiModule" + moduleDocs = "build/dokka-module/html/module" + dokkaMultiModuleRoot = "build/dokka/html/" } // Build API docs for all modules with dokka before running Knit tasks.named("knitPrepare") { - dependsOn("dokka") + dependsOn("dokkaGenerate") } @@ -133,17 +128,14 @@ subprojects { } } -// Knit relies on Dokka task and it's pretty convenient -tasks.register("dokka") { - dependsOn("dokkaHtmlMultiModule") -} - -tasks.withType().named("dokkaHtmlMultiModule") { - pluginsMapConfiguration.put("org.jetbrains.dokka.base.DokkaBase", """{ "templatesDir": "${projectDir.toString().replace("\\", "/")}/dokka-templates" }""") -} - +// apply conventions to root module and setup dependencies for aggregation +apply(plugin = "dokka-conventions") dependencies { - dokkaPlugin(libs.dokka.pathsaver) + subprojects.forEach { + if (it.name in documentedSubprojects) { + dokka(it) + } + } } // == NPM setup == @@ -157,19 +149,23 @@ logger.warn("Project is using Kotlin Gradle plugin version: ${project.getKotlinP // == projects lists and flags == // getters are required because of variable lazy initialization in Gradle -val unpublishedProjects get() = setOf( - "benchmark", - "guide", - "kotlinx-serialization-json-tests", - "proto-test-model", -) +val unpublishedProjects + get() = setOf( + "benchmark", + "guide", + "kotlinx-serialization-json-tests", + "proto-test-model", + ) val excludedFromBomProjects get() = unpublishedProjects + "kotlinx-serialization-bom" -val documentedSubprojects get() = setOf("kotlinx-serialization-core", - "kotlinx-serialization-json", - "kotlinx-serialization-json-okio", - "kotlinx-serialization-json-io", - "kotlinx-serialization-cbor", - "kotlinx-serialization-properties", - "kotlinx-serialization-hocon", - "kotlinx-serialization-protobuf") +val documentedSubprojects + get() = setOf( + "kotlinx-serialization-core", + "kotlinx-serialization-json", + "kotlinx-serialization-json-okio", + "kotlinx-serialization-json-io", + "kotlinx-serialization-cbor", + "kotlinx-serialization-properties", + "kotlinx-serialization-hocon", + "kotlinx-serialization-protobuf" + ) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 529b81c127..5c73ad8bd5 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -26,15 +26,8 @@ repositories { maven("https://oss.sonatype.org/content/repositories/snapshots") } - // kotlin-dev with space redirector - maven("https://cache-redirector.jetbrains.com/maven.pkg.jetbrains.space/kotlin/p/kotlin/dev") - - maven("https://maven.pkg.jetbrains.space/kotlin/p/dokka/dev") - // For Dokka that depends on kotlinx-html - maven("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven") - mavenCentral() - mavenLocal() + maven("https://redirector.kotlinlang.org/maven/dev") } kotlin { diff --git a/buildSrc/src/main/kotlin/Java9Modularity.kt b/buildSrc/src/main/kotlin/Java9Modularity.kt index 41558c2cce..abe933bb30 100644 --- a/buildSrc/src/main/kotlin/Java9Modularity.kt +++ b/buildSrc/src/main/kotlin/Java9Modularity.kt @@ -175,6 +175,7 @@ object Java9Modularity { val taskKotlinLanguageVersion = compilerOptions.languageVersion.orElse(KotlinVersion.DEFAULT) @OptIn(InternalKotlinGradlePluginApi::class) + @Suppress("DEPRECATION") if (taskKotlinLanguageVersion.get() < KotlinVersion.KOTLIN_2_0) { // part of work-around for https://youtrack.jetbrains.com/issue/KT-60541 @Suppress("INVISIBLE_MEMBER") diff --git a/buildSrc/src/main/kotlin/dokka-conventions.gradle.kts b/buildSrc/src/main/kotlin/dokka-conventions.gradle.kts index d30137276b..d64d46a911 100644 --- a/buildSrc/src/main/kotlin/dokka-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/dokka-conventions.gradle.kts @@ -1,10 +1,3 @@ -/* - * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -import org.jetbrains.dokka.gradle.* -import java.net.URI - /* * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ @@ -18,62 +11,24 @@ dependencies { dokkaPlugin(provider { extens.getByType().named("libs").findLibrary("dokka.pathsaver").get().get() }) } -tasks.withType().named("dokkaHtmlPartial") { - outputDirectory.set(file("build/dokka")) - - - pluginsMapConfiguration.put("org.jetbrains.dokka.base.DokkaBase", """{ "templatesDir": "${rootDir.resolve("dokka-templates").canonicalPath.replace('\\', '/')}" }""") - - dokkaSourceSets { - configureEach { - includes.from(rootDir.resolve("dokka/moduledoc.md").path) - - perPackageOption { - matchingRegex.set("kotlinx\\.serialization(\$|\\.).*") - reportUndocumented.set(true) - skipDeprecated.set(true) - } - - // Internal API - perPackageOption { - matchingRegex.set("kotlinx\\.serialization.internal(\$|\\.).*") - suppress.set(true) - } - - // Internal JSON API - perPackageOption { - matchingRegex.set("kotlinx\\.serialization.json.internal(\$|\\.).*") - suppress.set(true) - reportUndocumented.set(false) - } - - // Workaround for typealias - perPackageOption { - matchingRegex.set("kotlinx\\.serialization.protobuf.internal(\$|\\.).*") - suppress.set(true) - reportUndocumented.set(false) - } - - // Deprecated migrations - perPackageOption { - matchingRegex.set("kotlinx\\.protobuf(\$|\\.).*") - reportUndocumented.set(true) - skipDeprecated.set(true) - } +dokka { + pluginsConfiguration.html { + templatesDir = rootDir.resolve("dokka-templates") + } - // Deprecated migrations - perPackageOption { - matchingRegex.set("org\\.jetbrains\\.kotlinx\\.serialization\\.config(\$|\\.).*") - reportUndocumented.set(false) - skipDeprecated.set(true) - } + dokkaSourceSets.configureEach { + includes.from(rootDir.resolve("dokka/moduledoc.md").path) + reportUndocumented = true + skipDeprecated = true - sourceLink { - localDirectory.set(rootDir) + perPackageOption { + matchingRegex = ".*\\.internal(\\..*)?" + suppress = true + } - remoteUrl.set(URI("https://github.com/Kotlin/kotlinx.serialization/tree/master").toURL()) - remoteLineSuffix.set("#L") - } + sourceLink { + localDirectory = rootDir + remoteUrl("https://github.com/Kotlin/kotlinx.serialization/tree/master") } } -} \ No newline at end of file +} diff --git a/buildSrc/src/main/kotlin/global-compiler-options.gradle.kts b/buildSrc/src/main/kotlin/global-compiler-options.gradle.kts index c0e95c3586..aa70b9ba55 100644 --- a/buildSrc/src/main/kotlin/global-compiler-options.gradle.kts +++ b/buildSrc/src/main/kotlin/global-compiler-options.gradle.kts @@ -3,6 +3,12 @@ */ import org.jetbrains.kotlin.gradle.tasks.* +import kotlin.collections.joinToString + +val kotlinAdditionalCliOptions = providers.gradleProperty("kotlin_additional_cli_options") + .orNull?.let { options -> + options.removeSurrounding("\"").split(" ").filter { it.isNotBlank() } + } val globalCompilerArgs get() = listOf( @@ -17,6 +23,7 @@ tasks.withType(KotlinCompilationTask::class).configureEach { compilerOptions { // Unconditional compiler options freeCompilerArgs.addAll(globalCompilerArgs) + kotlinAdditionalCliOptions?.forEach { option -> freeCompilerArgs.add(option) } val isMainTaskName = name.startsWith("compileKotlin") if (isMainTaskName) { @@ -26,16 +33,7 @@ tasks.withType(KotlinCompilationTask::class).configureEach { null -> true // Werror is enabled by default else -> throw GradleException("Invalid kotlin_Werror_override value. Use 'enable' or 'disable'") } - allWarningsAsErrors = werrorEnabled - - // Add extra compiler options when -Werror is disabled - if (!werrorEnabled) { - freeCompilerArgs.addAll( - "-Wextra", - "-Xuse-fir-experimental-checkers" - ) - } } } } @@ -46,3 +44,10 @@ tasks.withType().configureEach { tasks.withType().configureEach { compilerOptions { freeCompilerArgs.add("-Xpartial-linkage-loglevel=ERROR") } } + +tasks.withType>().configureEach { + doFirst { + logger.info("Added Kotlin compiler flags: ${compilerOptions.freeCompilerArgs.get().joinToString(", ")}") + logger.info("allWarningsAsErrors=${compilerOptions.allWarningsAsErrors.get()}") + } +} diff --git a/core/api/kotlinx-serialization-core.api b/core/api/kotlinx-serialization-core.api index c8d0d35d77..1b15d35953 100644 --- a/core/api/kotlinx-serialization-core.api +++ b/core/api/kotlinx-serialization-core.api @@ -202,9 +202,19 @@ public final class kotlinx/serialization/builtins/BuiltinSerializersKt { public static final fun serializer (Lkotlin/jvm/internal/ShortCompanionObject;)Lkotlinx/serialization/KSerializer; public static final fun serializer (Lkotlin/jvm/internal/StringCompanionObject;)Lkotlinx/serialization/KSerializer; public static final fun serializer (Lkotlin/time/Duration$Companion;)Lkotlinx/serialization/KSerializer; + public static final fun serializer (Lkotlin/time/Instant$Companion;)Lkotlinx/serialization/KSerializer; public static final fun serializer (Lkotlin/uuid/Uuid$Companion;)Lkotlinx/serialization/KSerializer; } +public final class kotlinx/serialization/builtins/InstantComponentSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Lkotlinx/serialization/builtins/InstantComponentSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lkotlin/time/Instant; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lkotlin/time/Instant;)V +} + public final class kotlinx/serialization/builtins/LongAsStringSerializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lkotlinx/serialization/builtins/LongAsStringSerializer; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Long; @@ -361,6 +371,7 @@ public abstract class kotlinx/serialization/encoding/AbstractDecoder : kotlinx/s public final fun decodeByteElement (Lkotlinx/serialization/descriptors/SerialDescriptor;I)B public fun decodeChar ()C public final fun decodeCharElement (Lkotlinx/serialization/descriptors/SerialDescriptor;I)C + public fun decodeCollectionSize (Lkotlinx/serialization/descriptors/SerialDescriptor;)I public fun decodeDouble ()D public final fun decodeDoubleElement (Lkotlinx/serialization/descriptors/SerialDescriptor;I)D public fun decodeEnum (Lkotlinx/serialization/descriptors/SerialDescriptor;)I @@ -375,7 +386,10 @@ public abstract class kotlinx/serialization/encoding/AbstractDecoder : kotlinx/s public fun decodeNotNullMark ()Z public fun decodeNull ()Ljava/lang/Void; public final fun decodeNullableSerializableElement (Lkotlinx/serialization/descriptors/SerialDescriptor;ILkotlinx/serialization/DeserializationStrategy;Ljava/lang/Object;)Ljava/lang/Object; + public fun decodeNullableSerializableValue (Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; + public fun decodeSequentially ()Z public fun decodeSerializableElement (Lkotlinx/serialization/descriptors/SerialDescriptor;ILkotlinx/serialization/DeserializationStrategy;Ljava/lang/Object;)Ljava/lang/Object; + public fun decodeSerializableValue (Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; public fun decodeSerializableValue (Lkotlinx/serialization/DeserializationStrategy;Ljava/lang/Object;)Ljava/lang/Object; public static synthetic fun decodeSerializableValue$default (Lkotlinx/serialization/encoding/AbstractDecoder;Lkotlinx/serialization/DeserializationStrategy;Ljava/lang/Object;ILjava/lang/Object;)Ljava/lang/Object; public fun decodeShort ()S @@ -388,6 +402,7 @@ public abstract class kotlinx/serialization/encoding/AbstractDecoder : kotlinx/s public abstract class kotlinx/serialization/encoding/AbstractEncoder : kotlinx/serialization/encoding/CompositeEncoder, kotlinx/serialization/encoding/Encoder { public fun ()V + public fun beginCollection (Lkotlinx/serialization/descriptors/SerialDescriptor;I)Lkotlinx/serialization/encoding/CompositeEncoder; public fun beginStructure (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/CompositeEncoder; public fun encodeBoolean (Z)V public final fun encodeBooleanElement (Lkotlinx/serialization/descriptors/SerialDescriptor;IZ)V @@ -407,15 +422,19 @@ public abstract class kotlinx/serialization/encoding/AbstractEncoder : kotlinx/s public final fun encodeIntElement (Lkotlinx/serialization/descriptors/SerialDescriptor;II)V public fun encodeLong (J)V public final fun encodeLongElement (Lkotlinx/serialization/descriptors/SerialDescriptor;IJ)V + public fun encodeNotNullMark ()V public fun encodeNull ()V public fun encodeNullableSerializableElement (Lkotlinx/serialization/descriptors/SerialDescriptor;ILkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V + public fun encodeNullableSerializableValue (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V public fun encodeSerializableElement (Lkotlinx/serialization/descriptors/SerialDescriptor;ILkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V + public fun encodeSerializableValue (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V public fun encodeShort (S)V public final fun encodeShortElement (Lkotlinx/serialization/descriptors/SerialDescriptor;IS)V public fun encodeString (Ljava/lang/String;)V public final fun encodeStringElement (Lkotlinx/serialization/descriptors/SerialDescriptor;ILjava/lang/String;)V public fun encodeValue (Ljava/lang/Object;)V public fun endStructure (Lkotlinx/serialization/descriptors/SerialDescriptor;)V + public fun shouldEncodeElementDefault (Lkotlinx/serialization/descriptors/SerialDescriptor;I)Z } public abstract interface class kotlinx/serialization/encoding/ChunkedDecoder { @@ -795,6 +814,15 @@ public final class kotlinx/serialization/internal/InlineClassDescriptorKt { public static final fun InlinePrimitiveDescriptor (Ljava/lang/String;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/descriptors/SerialDescriptor; } +public final class kotlinx/serialization/internal/InstantSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Lkotlinx/serialization/internal/InstantSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lkotlin/time/Instant; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lkotlin/time/Instant;)V +} + public final class kotlinx/serialization/internal/IntArrayBuilder : kotlinx/serialization/internal/PrimitiveArrayBuilder { public synthetic fun build$kotlinx_serialization_core ()Ljava/lang/Object; } @@ -985,6 +1013,8 @@ public class kotlinx/serialization/internal/PluginGeneratedSerialDescriptor : ko public fun getSerialNames ()Ljava/util/Set; public fun hashCode ()I public fun isElementOptional (I)Z + public fun isInline ()Z + public fun isNullable ()Z public final fun pushAnnotation (Ljava/lang/annotation/Annotation;)V public final fun pushClassAnnotation (Ljava/lang/annotation/Annotation;)V public fun toString ()Ljava/lang/String; @@ -1075,6 +1105,7 @@ public abstract class kotlinx/serialization/internal/TaggedDecoder : kotlinx/ser public final fun decodeByteElement (Lkotlinx/serialization/descriptors/SerialDescriptor;I)B public final fun decodeChar ()C public final fun decodeCharElement (Lkotlinx/serialization/descriptors/SerialDescriptor;I)C + public fun decodeCollectionSize (Lkotlinx/serialization/descriptors/SerialDescriptor;)I public final fun decodeDouble ()D public final fun decodeDoubleElement (Lkotlinx/serialization/descriptors/SerialDescriptor;I)D public final fun decodeEnum (Lkotlinx/serialization/descriptors/SerialDescriptor;)I @@ -1089,7 +1120,10 @@ public abstract class kotlinx/serialization/internal/TaggedDecoder : kotlinx/ser public fun decodeNotNullMark ()Z public final fun decodeNull ()Ljava/lang/Void; public final fun decodeNullableSerializableElement (Lkotlinx/serialization/descriptors/SerialDescriptor;ILkotlinx/serialization/DeserializationStrategy;Ljava/lang/Object;)Ljava/lang/Object; + public fun decodeNullableSerializableValue (Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; + public fun decodeSequentially ()Z public final fun decodeSerializableElement (Lkotlinx/serialization/descriptors/SerialDescriptor;ILkotlinx/serialization/DeserializationStrategy;Ljava/lang/Object;)Ljava/lang/Object; + public fun decodeSerializableValue (Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; protected fun decodeSerializableValue (Lkotlinx/serialization/DeserializationStrategy;Ljava/lang/Object;)Ljava/lang/Object; public final fun decodeShort ()S public final fun decodeShortElement (Lkotlinx/serialization/descriptors/SerialDescriptor;I)S @@ -1120,6 +1154,7 @@ public abstract class kotlinx/serialization/internal/TaggedDecoder : kotlinx/ser public abstract class kotlinx/serialization/internal/TaggedEncoder : kotlinx/serialization/encoding/CompositeEncoder, kotlinx/serialization/encoding/Encoder { public fun ()V + public fun beginCollection (Lkotlinx/serialization/descriptors/SerialDescriptor;I)Lkotlinx/serialization/encoding/CompositeEncoder; public fun beginStructure (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/CompositeEncoder; public final fun encodeBoolean (Z)V public final fun encodeBooleanElement (Lkotlinx/serialization/descriptors/SerialDescriptor;IZ)V @@ -1141,7 +1176,9 @@ public abstract class kotlinx/serialization/internal/TaggedEncoder : kotlinx/ser public fun encodeNotNullMark ()V public fun encodeNull ()V public fun encodeNullableSerializableElement (Lkotlinx/serialization/descriptors/SerialDescriptor;ILkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V + public fun encodeNullableSerializableValue (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V public fun encodeSerializableElement (Lkotlinx/serialization/descriptors/SerialDescriptor;ILkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V + public fun encodeSerializableValue (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V public final fun encodeShort (S)V public final fun encodeShortElement (Lkotlinx/serialization/descriptors/SerialDescriptor;IS)V public final fun encodeString (Ljava/lang/String;)V @@ -1168,6 +1205,7 @@ public abstract class kotlinx/serialization/internal/TaggedEncoder : kotlinx/ser protected abstract fun getTag (Lkotlinx/serialization/descriptors/SerialDescriptor;I)Ljava/lang/Object; protected final fun popTag ()Ljava/lang/Object; protected final fun pushTag (Ljava/lang/Object;)V + public fun shouldEncodeElementDefault (Lkotlinx/serialization/descriptors/SerialDescriptor;I)Z } public final class kotlinx/serialization/internal/TripleSerializer : kotlinx/serialization/KSerializer { @@ -1314,6 +1352,7 @@ public final class kotlinx/serialization/modules/SerializersModuleBuilder : kotl public fun contextual (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V public final fun include (Lkotlinx/serialization/modules/SerializersModule;)V public fun polymorphic (Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V + public fun polymorphicDefault (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V public fun polymorphicDefaultDeserializer (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V public fun polymorphicDefaultSerializer (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V } diff --git a/core/api/kotlinx-serialization-core.klib.api b/core/api/kotlinx-serialization-core.klib.api index 041c115e26..5aefad0f2f 100644 --- a/core/api/kotlinx-serialization-core.klib.api +++ b/core/api/kotlinx-serialization-core.klib.api @@ -890,6 +890,14 @@ sealed class kotlinx.serialization.modules/SerializersModule { // kotlinx.serial final fun <#A1: kotlin/Any> getContextual(kotlin.reflect/KClass<#A1>): kotlinx.serialization/KSerializer<#A1>? // kotlinx.serialization.modules/SerializersModule.getContextual|getContextual(kotlin.reflect.KClass<0:0>){0§}[0] } +final object kotlinx.serialization.builtins/InstantComponentSerializer : kotlinx.serialization/KSerializer { // kotlinx.serialization.builtins/InstantComponentSerializer|null[0] + final val descriptor // kotlinx.serialization.builtins/InstantComponentSerializer.descriptor|{}descriptor[0] + final fun (): kotlinx.serialization.descriptors/SerialDescriptor // kotlinx.serialization.builtins/InstantComponentSerializer.descriptor.|(){}[0] + + final fun deserialize(kotlinx.serialization.encoding/Decoder): kotlin.time/Instant // kotlinx.serialization.builtins/InstantComponentSerializer.deserialize|deserialize(kotlinx.serialization.encoding.Decoder){}[0] + final fun serialize(kotlinx.serialization.encoding/Encoder, kotlin.time/Instant) // kotlinx.serialization.builtins/InstantComponentSerializer.serialize|serialize(kotlinx.serialization.encoding.Encoder;kotlin.time.Instant){}[0] +} + final object kotlinx.serialization.builtins/LongAsStringSerializer : kotlinx.serialization/KSerializer { // kotlinx.serialization.builtins/LongAsStringSerializer|null[0] final val descriptor // kotlinx.serialization.builtins/LongAsStringSerializer.descriptor|{}descriptor[0] final fun (): kotlinx.serialization.descriptors/SerialDescriptor // kotlinx.serialization.builtins/LongAsStringSerializer.descriptor.|(){}[0] @@ -956,6 +964,14 @@ final object kotlinx.serialization.internal/FloatSerializer : kotlinx.serializat final fun serialize(kotlinx.serialization.encoding/Encoder, kotlin/Float) // kotlinx.serialization.internal/FloatSerializer.serialize|serialize(kotlinx.serialization.encoding.Encoder;kotlin.Float){}[0] } +final object kotlinx.serialization.internal/InstantSerializer : kotlinx.serialization/KSerializer { // kotlinx.serialization.internal/InstantSerializer|null[0] + final val descriptor // kotlinx.serialization.internal/InstantSerializer.descriptor|{}descriptor[0] + final fun (): kotlinx.serialization.descriptors/SerialDescriptor // kotlinx.serialization.internal/InstantSerializer.descriptor.|(){}[0] + + final fun deserialize(kotlinx.serialization.encoding/Decoder): kotlin.time/Instant // kotlinx.serialization.internal/InstantSerializer.deserialize|deserialize(kotlinx.serialization.encoding.Decoder){}[0] + final fun serialize(kotlinx.serialization.encoding/Encoder, kotlin.time/Instant) // kotlinx.serialization.internal/InstantSerializer.serialize|serialize(kotlinx.serialization.encoding.Encoder;kotlin.time.Instant){}[0] +} + final object kotlinx.serialization.internal/IntArraySerializer : kotlinx.serialization.internal/PrimitiveArraySerializer, kotlinx.serialization/KSerializer // kotlinx.serialization.internal/IntArraySerializer|null[0] final object kotlinx.serialization.internal/IntSerializer : kotlinx.serialization/KSerializer { // kotlinx.serialization.internal/IntSerializer|null[0] @@ -1074,6 +1090,7 @@ final val kotlinx.serialization.modules/EmptySerializersModule // kotlinx.serial final fun (): kotlinx.serialization.modules/SerializersModule // kotlinx.serialization.modules/EmptySerializersModule.|(){}[0] final fun (kotlin.time/Duration.Companion).kotlinx.serialization.builtins/serializer(): kotlinx.serialization/KSerializer // kotlinx.serialization.builtins/serializer|serializer@kotlin.time.Duration.Companion(){}[0] +final fun (kotlin.time/Instant.Companion).kotlinx.serialization.builtins/serializer(): kotlinx.serialization/KSerializer // kotlinx.serialization.builtins/serializer|serializer@kotlin.time.Instant.Companion(){}[0] final fun (kotlin.uuid/Uuid.Companion).kotlinx.serialization.builtins/serializer(): kotlinx.serialization/KSerializer // kotlinx.serialization.builtins/serializer|serializer@kotlin.uuid.Uuid.Companion(){}[0] final fun (kotlin/Boolean.Companion).kotlinx.serialization.builtins/serializer(): kotlinx.serialization/KSerializer // kotlinx.serialization.builtins/serializer|serializer@kotlin.Boolean.Companion(){}[0] final fun (kotlin/Byte.Companion).kotlinx.serialization.builtins/serializer(): kotlinx.serialization/KSerializer // kotlinx.serialization.builtins/serializer|serializer@kotlin.Byte.Companion(){}[0] diff --git a/core/commonMain/src/kotlinx/serialization/Serializers.kt b/core/commonMain/src/kotlinx/serialization/Serializers.kt index 4e44d3d2b2..358bbe7956 100644 --- a/core/commonMain/src/kotlinx/serialization/Serializers.kt +++ b/core/commonMain/src/kotlinx/serialization/Serializers.kt @@ -23,7 +23,7 @@ import kotlin.reflect.* * This overload works with full type information, including type arguments and nullability, * and is a recommended way to retrieve a serializer. * For example, `serializer>()` returns [KSerializer] that is able - * to serialize and deserialize list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`. + * to serialize and deserialize a list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`. * * Variance of [T]'s type arguments is not used by the serialization and is not taken into account. * Star projections in [T]'s type arguments are prohibited. @@ -42,7 +42,7 @@ public inline fun serializer(): KSerializer { * This overload works with full type information, including type arguments and nullability, * and is a recommended way to retrieve a serializer. * For example, `serializer>()` returns [KSerializer] that is able - * to serialize and deserialize list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`. + * to serialize and deserialize a list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`. * * Variance of [T]'s type arguments is not used by the serialization and is not taken into account. * Star projections in [T]'s type arguments are prohibited. @@ -60,12 +60,17 @@ public inline fun SerializersModule.serializer(): KSerializer { * * This overload works with full type information, including type arguments and nullability, * and is a recommended way to retrieve a serializer. - * For example, `serializer>>()` returns [KSerializer] that is able - * to serialize and deserialize list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`. + * For example, `serializer(typeOf>())` returns [KSerializer] that is able + * to serialize and deserialize a list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`. * * Variance of [type]'s type arguments is not used by the serialization and is not taken into account. * Star projections in [type]'s arguments are prohibited. * + * **Pitfall**: the returned serializer may return incorrect results or throw a [ClassCastException] if it receives + * a value that's not a valid instance of the [KType], even though the type allows passing such a value. + * Consider using the `serializer()` overload accepting a type argument (for example, `serializer>()`), + * which returns the serializer with the correct type. + * * @throws SerializationException if serializer cannot be created (provided [type] or its type argument is not serializable). * @throws IllegalArgumentException if any of [type]'s arguments contains star projection */ @@ -80,11 +85,16 @@ public fun serializer(type: KType): KSerializer = EmptySerializersModule() * The nullability of returned serializer is specified using the [isNullable]. * * Note that it is impossible to create an array serializer with this method, - * as array serializer needs additional information: type token for an element type. + * as an array serializer needs additional information: type token for an element type. * To create array serializer, use overload with [KType] or [ArraySerializer] directly. * * Caching on JVM platform is disabled for this function, so it may work slower than an overload with [KType]. * + * **Pitfall**: the returned serializer may return incorrect results or throw a [ClassCastException] if it receives + * a value that's not a valid instance of the [KClass], even though the type allows passing such a value. + * Consider using the `serializer()` overload accepting a type argument (for example, `serializer>()`), + * which returns the serializer with the correct type. + * * @throws SerializationException if serializer cannot be created (provided [kClass] or its type argument is not serializable) * @throws SerializationException if [kClass] is a `kotlin.Array` * @throws SerializationException if size of [typeArgumentsSerializers] does not match the expected generic parameters count @@ -102,12 +112,15 @@ public fun serializer( * * This overload works with full type information, including type arguments and nullability, * and is a recommended way to retrieve a serializer. - * For example, `serializerOrNull>>()` returns [KSerializer] that is able - * to serialize and deserialize list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`. + * For example, `serializerOrNull(typeOf>())` returns [KSerializer] that is able + * to serialize and deserialize a list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`. * * Variance of [type]'s arguments is not used by the serialization and is not taken into account. * Star projections in [type]'s arguments are prohibited. * + * **Pitfall**: the returned serializer may return incorrect results or throw a [ClassCastException] if it receives + * a value that's not a valid instance of the [KType], even though the type allows passing such a value. + * * @return [KSerializer] for the given [type] or `null` if serializer cannot be created (given [type] or its type argument is not serializable). * @throws IllegalArgumentException if any of [type]'s arguments contains star projection */ @@ -120,12 +133,18 @@ public fun serializerOrNull(type: KType): KSerializer? = EmptySerializersM * * This overload works with full type information, including type arguments and nullability, * and is a recommended way to retrieve a serializer. - * For example, `serializer>>()` returns [KSerializer] that is able - * to serialize and deserialize list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`. + * For example, `serializer(typeOf>())` returns [KSerializer] that is able + * to serialize and deserialize a list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`. * * Variance of [type]'s arguments is not used by the serialization and is not taken into account. * Star projections in [type]'s arguments are prohibited. * + * **Pitfall**: the returned serializer may return incorrect results or throw a [ClassCastException] if it receives + * a value that's not a valid instance of the [KType], even though the type allows passing such a value. + * Consider using the `serializer()` overload accepting a type argument + * (for example, `module.serializer>()`), + * which returns the serializer with the correct type. + * * @throws SerializationException if serializer cannot be created (provided [type] or its type argument is not serializable and is not registered in [this] module). * @throws IllegalArgumentException if any of [type]'s arguments contains star projection */ @@ -143,11 +162,17 @@ public fun SerializersModule.serializer(type: KType): KSerializer = * The nullability of returned serializer is specified using the [isNullable]. * * Note that it is impossible to create an array serializer with this method, - * as array serializer needs additional information: type token for an element type. + * as an array serializer needs additional information: type token for an element type. * To create array serializer, use overload with [KType] or [ArraySerializer] directly. * * Caching on JVM platform is disabled for this function, so it may work slower than an overload with [KType]. * + * **Pitfall**: the returned serializer may return incorrect results or throw a [ClassCastException] if it receives + * a value that's not a valid instance of the [KClass], even though the type allows passing such a value. + * Consider using the `serializer()` overload accepting a type argument + * (for example, `module.serializer>()`), + * which returns the serializer with the correct type. + * * @throws SerializationException if serializer cannot be created (provided [kClass] or its type argument is not serializable and is not registered in [this] module) * @throws SerializationException if [kClass] is a `kotlin.Array` * @throws SerializationException if size of [typeArgumentsSerializers] does not match the expected generic parameters count @@ -168,12 +193,15 @@ public fun SerializersModule.serializer( * * This overload works with full type information, including type arguments and nullability, * and is a recommended way to retrieve a serializer. - * For example, `serializerOrNull>>()` returns [KSerializer] that is able - * to serialize and deserialize list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`. + * For example, `serializerOrNull(typeOf>())` returns [KSerializer] that is able + * to serialize and deserialize a list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`. * * Variance of [type]'s arguments is not used by the serialization and is not taken into account. * Star projections in [type]'s arguments are prohibited. * + * **Pitfall**: the returned serializer may return incorrect results or throw a [ClassCastException] if it receives + * a value that's not a valid instance of the [KType], even though the type allows passing such a value. + * * @return [KSerializer] for the given [type] or `null` if serializer cannot be created (given [type] or its type argument is not serializable and is not registered in [this] module). * @throws IllegalArgumentException if any of [type]'s arguments contains star projection */ @@ -276,24 +304,24 @@ internal fun SerializersModule.serializersForParameters( * The given class must be annotated with [Serializable] or be one of the built-in types. * * This method uses platform-specific reflection available for the given erased `KClass` - * and is not recommended to use this method for anything, but last-ditch resort, e.g. - * when all type info is lost, your application has crashed and it is the final attempt to log or send some serializable data. + * and is not recommended to use this method for anything, but last-ditch resort, e.g., + * when all type info is lost, your application has crashed, and it is the final attempt to log or send some serializable data. * * The recommended way to retrieve the serializer is inline [serializer] function and [`serializer(KType)`][serializer] * * This API is not guaranteed to work consistently across different platforms or - * to work in cases that slightly differ from "plain @Serializable class" and have platform and reflection specific limitations. + * to work in cases that slightly differ from "plain @Serializable class" and have platform- and reflection-specific limitations. * * ### Constraints * This paragraph explains known (but not all!) constraints of the `serializer()` implementation. - * Please note that they are not bugs, but implementation restrictions that we cannot workaround. + * Please note that they are not bugs but implementation restrictions that we cannot work around. * * * This method may behave differently on JVM, JS and Native because of runtime reflection differences * * Serializers for classes with generic parameters are ignored by this method - * * External serializers generated with `Serializer(forClass = )` are not lookuped consistently - * * Serializers for classes with named companion objects are not lookuped consistently + * * External serializers generated with `Serializer(forClass = )` are not looked up consistently + * * Serializers for classes with named companion objects are not looked up consistently * - * @throws SerializationException if serializer can't be found. + * @throws SerializationException if the serializer can't be found. */ @InternalSerializationApi public fun KClass.serializer(): KSerializer = serializerOrNull() ?: serializerNotRegistered() @@ -302,20 +330,20 @@ public fun KClass.serializer(): KSerializer = serializerOrNull() * Retrieves a [KSerializer] for the given [KClass] or returns `null` if none is found. * The given class must be annotated with [Serializable] or be one of the built-in types. * This method uses platform-specific reflection available for the given erased `KClass` - * and it is not recommended to use this method for anything, but last-ditch resort, e.g. - * when all type info is lost, your application has crashed and it is the final attempt to log or send some serializable data. + * and it is not recommended to use this method for anything, but last-ditch resort, e.g., + * when all type info is lost, your application has crashed, and it is the final attempt to log or send some serializable data. * * This API is not guaranteed to work consistently across different platforms or * to work in cases that slightly differ from "plain @Serializable class". * * ### Constraints * This paragraph explains known (but not all!) constraints of the `serializerOrNull()` implementation. - * Please note that they are not bugs, but implementation restrictions that we cannot workaround. + * Please note that they are not bugs but implementation restrictions that we cannot work around. * * * This method may behave differently on JVM, JS and Native because of runtime reflection differences * * Serializers for classes with generic parameters are ignored by this method - * * External serializers generated with `Serializer(forClass = )` are not lookuped consistently - * * Serializers for classes with named companion objects are not lookuped consistently + * * External serializers generated with `Serializer(forClass = )` are not looked up consistently + * * Serializers for classes with named companion objects are not looked up consistently */ @InternalSerializationApi public fun KClass.serializerOrNull(): KSerializer? = diff --git a/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt b/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt index c481e3adf8..aedd4fe874 100644 --- a/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt +++ b/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt @@ -10,6 +10,8 @@ import kotlinx.serialization.internal.* import kotlin.reflect.* import kotlinx.serialization.descriptors.* import kotlin.time.Duration +import kotlin.time.ExperimentalTime +import kotlin.time.Instant import kotlin.uuid.* /** @@ -245,12 +247,30 @@ public fun UShort.Companion.serializer(): KSerializer = UShortSerializer /** * Returns serializer for [Duration]. - * It is serialized as a string that represents a duration in the ISO-8601-2 format. + * It is serialized as a string that represents a duration in the format used by [Duration.toIsoString], + * that is, the ISO-8601-2 format. * - * The result of serialization is similar to calling [Duration.toIsoString], for deserialization is [Duration.parseIsoString]. + * For deserialization, [Duration.parseIsoString] is used. + * + * @see Duration.toIsoString + * @see Duration.parseIsoString */ public fun Duration.Companion.serializer(): KSerializer = DurationSerializer +/** + * Returns serializer for [Instant]. + * It is serialized as a string that represents an instant in the format used by [Instant.toString] + * and described in ISO-8601-1:2019, 5.4.2.1b). + * + * Deserialization is case-insensitive. + * More details can be found in the documentation of [Instant.toString] and [Instant.parse] functions. + * + * @see Instant.toString + * @see Instant.parse + */ +@ExperimentalTime +public fun Instant.Companion.serializer(): KSerializer = InstantSerializer + /** * Returns serializer for [Uuid]. * Serializer operates with a standard UUID string representation, also known as "hex-and-dash" format — diff --git a/core/commonMain/src/kotlinx/serialization/builtins/InstantComponentSerializer.kt b/core/commonMain/src/kotlinx/serialization/builtins/InstantComponentSerializer.kt new file mode 100644 index 0000000000..3aaedccb9e --- /dev/null +++ b/core/commonMain/src/kotlinx/serialization/builtins/InstantComponentSerializer.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2025-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization.builtins + +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlin.time.ExperimentalTime +import kotlin.time.Instant + +/** + * Serializer that encodes and decodes [Instant] as its second and nanosecond components of the Unix time. + * + * JSON example: `{"epochSeconds":1607505416,"nanosecondsOfSecond":124000}`. + */ +@ExperimentalTime +public object InstantComponentSerializer : KSerializer { + + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("kotlinx.serialization.InstantComponentSerializer") { + element("epochSeconds") + element("nanosecondsOfSecond", isOptional = true) + } + + @OptIn(ExperimentalSerializationApi::class) + override fun deserialize(decoder: Decoder): Instant = + decoder.decodeStructure(descriptor) { + var epochSecondsNotSeen = true + var epochSeconds: Long = 0 + var nanosecondsOfSecond = 0 + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> { + epochSecondsNotSeen = false + epochSeconds = decodeLongElement(descriptor, 0) + } + 1 -> nanosecondsOfSecond = decodeIntElement(descriptor, 1) + CompositeDecoder.DECODE_DONE -> break + else -> throw SerializationException("Unexpected index: $index") + } + } + if (epochSecondsNotSeen) throw MissingFieldException( + missingField = "epochSeconds", + serialName = descriptor.serialName + ) + Instant.fromEpochSeconds(epochSeconds, nanosecondsOfSecond) + } + + @OptIn(ExperimentalSerializationApi::class) + override fun serialize(encoder: Encoder, value: Instant) { + encoder.encodeStructure(descriptor) { + encodeLongElement(descriptor, 0, value.epochSeconds) + if (value.nanosecondsOfSecond != 0 || shouldEncodeElementDefault(descriptor, 1)) { + encodeIntElement(descriptor, 1, value.nanosecondsOfSecond) + } + } + } + +} diff --git a/core/commonMain/src/kotlinx/serialization/internal/BuiltInSerializers.kt b/core/commonMain/src/kotlinx/serialization/internal/BuiltInSerializers.kt index fbc5dc1476..8db48f72e9 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/BuiltInSerializers.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/BuiltInSerializers.kt @@ -10,6 +10,8 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlin.time.Duration +import kotlin.time.ExperimentalTime +import kotlin.time.Instant import kotlin.uuid.* @@ -39,6 +41,20 @@ internal object NothingSerializer : KSerializer { } } +@PublishedApi +@ExperimentalTime +internal object InstantSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("kotlin.time.Instant", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: Instant) { + encoder.encodeString(value.toString()) + } + + override fun deserialize(decoder: Decoder): Instant { + return Instant.parse(decoder.decodeString()) + } +} + @PublishedApi @ExperimentalUuidApi internal object UuidSerializer: KSerializer { diff --git a/core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt b/core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt index 859818aa29..6115f8fd1d 100644 --- a/core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt +++ b/core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt @@ -10,7 +10,6 @@ import kotlinx.serialization.descriptors.* 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/jsMain/src/kotlinx/serialization/internal/Platform.kt b/core/jsMain/src/kotlinx/serialization/internal/Platform.kt index e423afca2a..63f48bcb34 100644 --- a/core/jsMain/src/kotlinx/serialization/internal/Platform.kt +++ b/core/jsMain/src/kotlinx/serialization/internal/Platform.kt @@ -26,7 +26,7 @@ internal actual fun KClass.compiledSerializerImpl(): KSerializer else this.js.asDynamic().Companion?.serializer() ) as? KSerializer -internal actual fun KClass.isInterface(): Boolean = isInterface +internal actual fun KClass.isInterface(): Boolean = isInterfaceHack internal actual fun createCache(factory: (KClass<*>) -> KSerializer?): SerializerCache { return object: SerializerCache { @@ -73,15 +73,18 @@ internal actual fun isReferenceArray(rootClass: KClass): Boolean = rootClas * WARNING: may be broken in arbitrary time in the future without notice * * Should be eventually replaced with compiler intrinsics + * + * TODO: Remove this when KT-78581 lands into a 2.2.20 release */ -private val KClass<*>.isInterface: Boolean +private val KClass<*>.isInterfaceHack: Boolean get(): Boolean { // .js throws an exception for Nothing if (this === Nothing::class) return false return js.asDynamic().`$metadata$`?.kind == "interface" } -@OptIn(ExperimentalUnsignedTypes::class, ExperimentalUuidApi::class, ExperimentalSerializationApi::class) +@OptIn(ExperimentalUnsignedTypes::class, ExperimentalUuidApi::class, ExperimentalSerializationApi::class, + ExperimentalTime::class) internal actual fun initBuiltins(): Map, KSerializer<*>> = mapOf( String::class to String.serializer(), Char::class to Char.serializer(), @@ -111,5 +114,6 @@ internal actual fun initBuiltins(): Map, KSerializer<*>> = mapOf( Unit::class to Unit.serializer(), Nothing::class to NothingSerializer(), Duration::class to Duration.serializer(), + Instant::class to Instant.serializer(), Uuid::class to Uuid.serializer() ) diff --git a/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt b/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt index b838a0fe52..65d38fba70 100644 --- a/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt +++ b/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt @@ -201,6 +201,9 @@ internal actual fun initBuiltins(): Map, KSerializer<*>> = buildMap { } @OptIn(ExperimentalUuidApi::class) loadSafe { put(Uuid::class, Uuid.serializer()) } + + @OptIn(ExperimentalTime::class) + loadSafe { put(Instant::class, Instant.serializer()) } } // Reference classes in [block] ignoring any exceptions related to class loading diff --git a/core/nativeMain/src/kotlinx/serialization/internal/Platform.kt b/core/nativeMain/src/kotlinx/serialization/internal/Platform.kt index c2e9fdd7e0..7e2423a76e 100644 --- a/core/nativeMain/src/kotlinx/serialization/internal/Platform.kt +++ b/core/nativeMain/src/kotlinx/serialization/internal/Platform.kt @@ -75,7 +75,8 @@ private fun arrayOfAnyNulls(size: Int): Array = arrayOfNulls(size) a internal actual fun isReferenceArray(rootClass: KClass): Boolean = rootClass == Array::class -@OptIn(ExperimentalUnsignedTypes::class, ExperimentalUuidApi::class, ExperimentalSerializationApi::class) +@OptIn(ExperimentalUnsignedTypes::class, ExperimentalUuidApi::class, ExperimentalSerializationApi::class, + ExperimentalTime::class) internal actual fun initBuiltins(): Map, KSerializer<*>> = mapOf( String::class to String.serializer(), Char::class to Char.serializer(), @@ -105,5 +106,6 @@ internal actual fun initBuiltins(): Map, KSerializer<*>> = mapOf( Unit::class to Unit.serializer(), Nothing::class to NothingSerializer(), Duration::class to Duration.serializer(), + Instant::class to Instant.serializer(), Uuid::class to Uuid.serializer() ) diff --git a/core/wasmMain/src/kotlinx/serialization/internal/Platform.kt b/core/wasmMain/src/kotlinx/serialization/internal/Platform.kt index ecfee8ede8..381d6a4d68 100644 --- a/core/wasmMain/src/kotlinx/serialization/internal/Platform.kt +++ b/core/wasmMain/src/kotlinx/serialization/internal/Platform.kt @@ -65,7 +65,8 @@ internal actual fun ArrayList.toNativeArrayImpl(eClass: KCl internal actual fun isReferenceArray(rootClass: KClass): Boolean = rootClass == Array::class -@OptIn(ExperimentalUnsignedTypes::class, ExperimentalUuidApi::class, ExperimentalSerializationApi::class) +@OptIn(ExperimentalUnsignedTypes::class, ExperimentalUuidApi::class, ExperimentalSerializationApi::class, + ExperimentalTime::class) internal actual fun initBuiltins(): Map, KSerializer<*>> = mapOf( String::class to String.serializer(), Char::class to Char.serializer(), @@ -95,5 +96,6 @@ internal actual fun initBuiltins(): Map, KSerializer<*>> = mapOf( Unit::class to Unit.serializer(), Nothing::class to NothingSerializer(), Duration::class to Duration.serializer(), + Instant::class to Instant.serializer(), Uuid::class to Uuid.serializer() ) diff --git a/formats/cbor/api/kotlinx-serialization-cbor.api b/formats/cbor/api/kotlinx-serialization-cbor.api index 8b580321fb..e1e37801f6 100644 --- a/formats/cbor/api/kotlinx-serialization-cbor.api +++ b/formats/cbor/api/kotlinx-serialization-cbor.api @@ -1,7 +1,7 @@ public abstract interface annotation class kotlinx/serialization/cbor/ByteString : java/lang/annotation/Annotation { } -public synthetic class kotlinx/serialization/cbor/ByteString$Impl : kotlinx/serialization/cbor/ByteString { +public final synthetic class kotlinx/serialization/cbor/ByteString$Impl : kotlinx/serialization/cbor/ByteString { public fun ()V } @@ -21,7 +21,7 @@ public final class kotlinx/serialization/cbor/Cbor$Default : kotlinx/serializati public abstract interface annotation class kotlinx/serialization/cbor/CborArray : java/lang/annotation/Annotation { } -public synthetic class kotlinx/serialization/cbor/CborArray$Impl : kotlinx/serialization/cbor/CborArray { +public final synthetic class kotlinx/serialization/cbor/CborArray$Impl : kotlinx/serialization/cbor/CborArray { public fun ()V } @@ -96,7 +96,7 @@ public abstract interface annotation class kotlinx/serialization/cbor/CborLabel public abstract fun label ()J } -public synthetic class kotlinx/serialization/cbor/CborLabel$Impl : kotlinx/serialization/cbor/CborLabel { +public final synthetic class kotlinx/serialization/cbor/CborLabel$Impl : kotlinx/serialization/cbor/CborLabel { public fun (J)V public final synthetic fun label ()J } @@ -125,7 +125,7 @@ public abstract interface annotation class kotlinx/serialization/cbor/KeyTags : public abstract fun tags ()[J } -public synthetic class kotlinx/serialization/cbor/KeyTags$Impl : kotlinx/serialization/cbor/KeyTags { +public final synthetic class kotlinx/serialization/cbor/KeyTags$Impl : kotlinx/serialization/cbor/KeyTags { public synthetic fun ([JLkotlin/jvm/internal/DefaultConstructorMarker;)V public final synthetic fun tags ()[J } @@ -134,7 +134,7 @@ public abstract interface annotation class kotlinx/serialization/cbor/ObjectTags public abstract fun tags ()[J } -public synthetic class kotlinx/serialization/cbor/ObjectTags$Impl : kotlinx/serialization/cbor/ObjectTags { +public final synthetic class kotlinx/serialization/cbor/ObjectTags$Impl : kotlinx/serialization/cbor/ObjectTags { public synthetic fun ([JLkotlin/jvm/internal/DefaultConstructorMarker;)V public final synthetic fun tags ()[J } @@ -143,7 +143,7 @@ public abstract interface annotation class kotlinx/serialization/cbor/ValueTags public abstract fun tags ()[J } -public synthetic class kotlinx/serialization/cbor/ValueTags$Impl : kotlinx/serialization/cbor/ValueTags { +public final synthetic class kotlinx/serialization/cbor/ValueTags$Impl : kotlinx/serialization/cbor/ValueTags { public synthetic fun ([JLkotlin/jvm/internal/DefaultConstructorMarker;)V public final synthetic fun tags ()[J } diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/Cbor.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/Cbor.kt index 21293a9231..7b618a8c3b 100644 --- a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/Cbor.kt +++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/Cbor.kt @@ -88,8 +88,27 @@ public sealed class Cbor( val reader = CborReader(this, CborParser(stream, configuration.verifyObjectTags)) return reader.decodeSerializableValue(deserializer) } + + public fun decodeFromCbor(deserializer: DeserializationStrategy, element: CborElement): T { + val reader = CborReader(this, StructuredCborParser(element, configuration.verifyObjectTags)) + return reader.decodeSerializableValue(deserializer) + } + + public fun encodeToCbor(serializer: SerializationStrategy, value: T): CborElement { + val writer = StructuredCborWriter(this) + writer.encodeSerializableValue(serializer, value) + return writer.finalize() + } } +@ExperimentalSerializationApi +public inline fun Cbor.encodeToCbor(value: T): CborElement = + encodeToCbor(serializersModule.serializer(), value) + +@ExperimentalSerializationApi +public inline fun Cbor.decodeFromCbor(element: CborElement): T = + decodeFromCbor(serializersModule.serializer(), element) + @OptIn(ExperimentalSerializationApi::class) private class CborImpl( configuration: CborConfiguration, @@ -108,18 +127,20 @@ private class CborImpl( public fun Cbor(from: Cbor = Cbor, builderAction: CborBuilder.() -> Unit): Cbor { val builder = CborBuilder(from) builder.builderAction() - return CborImpl(CborConfiguration( - builder.encodeDefaults, - builder.ignoreUnknownKeys, - builder.encodeKeyTags, - builder.encodeValueTags, - builder.encodeObjectTags, - builder.verifyKeyTags, - builder.verifyValueTags, - builder.verifyObjectTags, - builder.useDefiniteLengthEncoding, - builder.preferCborLabelsOverNames, - builder.alwaysUseByteString), + return CborImpl( + CborConfiguration( + builder.encodeDefaults, + builder.ignoreUnknownKeys, + builder.encodeKeyTags, + builder.encodeValueTags, + builder.encodeObjectTags, + builder.verifyKeyTags, + builder.verifyValueTags, + builder.verifyObjectTags, + builder.useDefiniteLengthEncoding, + builder.preferCborLabelsOverNames, + builder.alwaysUseByteString + ), builder.serializersModule ) } diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborDecoder.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborDecoder.kt index 13a773f3fa..da6bde3c06 100644 --- a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborDecoder.kt +++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborDecoder.kt @@ -31,4 +31,15 @@ public interface CborDecoder : Decoder { * Exposes the current [Cbor] instance and all its configuration flags. Useful for low-level custom serializers. */ public val cbor: Cbor + + /** + * Decodes the next element in the current input as [CborElement]. + * The type of the decoded element depends on the current state of the input and, when received + * by [serializer][KSerializer] in its [KSerializer.serialize] method, the type of the token directly matches + * the [kind][SerialDescriptor.kind]. + * + * This method is allowed to invoke only as the part of the whole deserialization process of the class, + * calling this method after invoking [beginStructure] or any `decode*` method will lead to unspecified behaviour. + */ + public fun decodeCborElement(): CborElement } diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborElement.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborElement.kt new file mode 100644 index 0000000000..c6f610b18d --- /dev/null +++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborElement.kt @@ -0,0 +1,244 @@ +/* + * Copyright 2017-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:Suppress("unused") +@file:OptIn(ExperimentalUnsignedTypes::class) + +package kotlinx.serialization.cbor + +import kotlinx.serialization.* +import kotlinx.serialization.cbor.internal.* + +/** + * Class representing single CBOR element. + * Can be [CborPrimitive], [CborMap] or [CborList]. + * + * [CborElement.toString] properly prints CBOR tree as a human-readable representation. + * Whole hierarchy is serializable, but only when used with [Cbor] as [CborElement] is purely CBOR-specific structure + * which has a meaningful schemaless semantics only for CBOR. + * + * The whole hierarchy is [serializable][Serializable] only by [Cbor] format. + */ +@Serializable(with = CborElementSerializer::class) +public sealed class CborElement( + /** + * CBOR tags associated with this element. + * Tags are optional semantic tagging of other major types (major type 6). + * See [RFC 8949 3.4. Tagging of Items](https://datatracker.ietf.org/doc/html/rfc8949#name-tagging-of-items). + */ + @OptIn(ExperimentalUnsignedTypes::class) + tags: ULongArray = ulongArrayOf() + +) { + /** + * CBOR tags associated with this element. + * Tags are optional semantic tagging of other major types (major type 6). + * See [RFC 8949 3.4. Tagging of Items](https://datatracker.ietf.org/doc/html/rfc8949#name-tagging-of-items). + */ + @OptIn(ExperimentalUnsignedTypes::class) + public var tags: ULongArray = tags + internal set + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is CborElement) return false + + if (!tags.contentEquals(other.tags)) return false + + return true + } + + override fun hashCode(): Int { + return tags.contentHashCode() + } + +} + +/** + * Class representing CBOR primitive value. + * CBOR primitives include numbers, strings, booleans, byte arrays and special null value [CborNull]. + */ +@Serializable(with = CborPrimitiveSerializer::class) +public sealed class CborPrimitive( + public val value: T, + tags: ULongArray = ulongArrayOf() +) : CborElement(tags) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is CborPrimitive<*>) return false + if (!super.equals(other)) return false + + if (value != other.value) return false + + return true + } + + override fun hashCode(): Int { + var result = super.hashCode() + result = 31 * result + value.hashCode() + return result + } + + override fun toString(): String { + return "CborPrimitive(" + + "kind=${value::class.simpleName}, " + + "tags=${tags.joinToString()}, " + + "value=$value" + + ")" + } +} + +public sealed class CborInt( + tags: ULongArray = ulongArrayOf(), + value: T, +) : CborPrimitive(value, tags) { + public companion object { + public operator fun invoke( + value: Long, + tags: ULongArray = ulongArrayOf() + ): CborInt<*> = if (value >= 0) CborPositiveInt(value.toULong(), tags) else CborNegativeInt(value, tags) + + public operator fun invoke( + value: ULong, + tags: ULongArray = ulongArrayOf() + ): CborInt = CborPositiveInt(value, tags) + } +} + +/** + * Class representing signed CBOR integer (major type 1). + */ +@Serializable(with = CborIntSerializer::class) +public class CborNegativeInt( + value: Long, + tags: ULongArray = ulongArrayOf() +) : CborInt(tags, value) { + init { + require(value < 0) { "Number must be negative: $value" } + } +} + +/** + * Class representing unsigned CBOR integer (major type 0). + */ +@Serializable(with = CborUIntSerializer::class) +public class CborPositiveInt( + value: ULong, + tags: ULongArray = ulongArrayOf() +) : CborInt(tags, value) + +/** + * Class representing CBOR floating point value (major type 7). + */ +@Serializable(with = CborDoubleSerializer::class) +public class CborDouble( + value: Double, + tags: ULongArray = ulongArrayOf() +) : CborPrimitive(value, tags) + +/** + * Class representing CBOR string value. + */ +@Serializable(with = CborStringSerializer::class) +public class CborString( + value: String, + tags: ULongArray = ulongArrayOf() +) : CborPrimitive(value, tags) + +/** + * Class representing CBOR boolean value. + */ +@Serializable(with = CborBooleanSerializer::class) +public class CborBoolean( + value: Boolean, + tags: ULongArray = ulongArrayOf() +) : CborPrimitive(value, tags) + +/** + * Class representing CBOR byte string value. + */ +@Serializable(with = CborByteStringSerializer::class) +public class CborByteString( + value: ByteArray, + tags: ULongArray = ulongArrayOf() +) : CborPrimitive(value, tags) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is CborByteString) return false + if (!tags.contentEquals(other.tags)) return false + return value.contentEquals(other.value) + } + + override fun hashCode(): Int { + var result = tags.contentHashCode() + result = 31 * result + (value.contentHashCode()) + return result + } + + override fun toString(): String { + return "CborPrimitive(" + + "kind=${value::class.simpleName}, " + + "tags=${tags.joinToString()}, " + + "value=h'${value.toHexString()}" + + ")" + } +} + +/** + * Class representing CBOR `null` value + */ +@Serializable(with = CborNullSerializer::class) +public class CborNull(tags: ULongArray = ulongArrayOf()) : CborPrimitive(Unit, tags) + +/** + * Class representing CBOR map, consisting of key-value pairs, where both key and value are arbitrary [CborElement] + * + * Since this class also implements [Map] interface, you can use + * traditional methods like [Map.get] or [Map.getValue] to obtain CBOR elements. + */ +@Serializable(with = CborMapSerializer::class) +public class CborMap( + private val content: Map, + tags: ULongArray = ulongArrayOf() +) : CborElement(tags), Map by content { + + public override fun equals(other: Any?): Boolean = + other is CborMap && other.content == content && other.tags.contentEquals(tags) + + public override fun hashCode(): Int = content.hashCode() * 31 + tags.contentHashCode() + + override fun toString(): String { + return "CborMap(" + + "tags=${tags.joinToString()}, " + + "content=$content" + + ")" + } + +} + +/** + * Class representing CBOR array, consisting of CBOR elements. + * + * Since this class also implements [List] interface, you can use + * traditional methods like [List.get] or [List.size] to obtain CBOR elements. + */ +@Serializable(with = CborListSerializer::class) +public class CborList( + private val content: List, + tags: ULongArray = ulongArrayOf() +) : CborElement(tags), List by content { + + public override fun equals(other: Any?): Boolean = + other is CborList && other.content == content && other.tags.contentEquals(tags) + + public override fun hashCode(): Int = content.hashCode() * 31 + tags.contentHashCode() + + override fun toString(): String { + return "CborList(" + + "tags=${tags.joinToString()}, " + + "content=$content" + + ")" + } + +} \ No newline at end of file diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborElementSerializers.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborElementSerializers.kt new file mode 100644 index 0000000000..3c968d0714 --- /dev/null +++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborElementSerializers.kt @@ -0,0 +1,304 @@ +/* + * Copyright 2017-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +@file:OptIn(ExperimentalSerializationApi::class, ExperimentalUnsignedTypes::class) + +package kotlinx.serialization.cbor.internal + +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.cbor.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* + +/** + * Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborElement]. + * It can only be used by with [Cbor] format and its input ([CborDecoder] and [CborEncoder]). + */ +internal object CborElementSerializer : KSerializer { + override val descriptor: SerialDescriptor = + buildSerialDescriptor("kotlinx.serialization.cbor.CborElement", PolymorphicKind.SEALED) { + // Resolve cyclic dependency in descriptors by late binding + element("CborPrimitive", defer { CborPrimitiveSerializer.descriptor }) + element("CborNull", defer { CborNullSerializer.descriptor }) + element("CborString", defer { CborStringSerializer.descriptor }) + element("CborBoolean", defer { CborBooleanSerializer.descriptor }) + element("CborByteString", defer { CborByteStringSerializer.descriptor }) + element("CborMap", defer { CborMapSerializer.descriptor }) + element("CborList", defer { CborListSerializer.descriptor }) + element("CborDouble", defer { CborDoubleSerializer.descriptor }) + element("CborInt", defer { CborIntSerializer.descriptor }) + element("CborUInt", defer { CborUIntSerializer.descriptor }) + } + + override fun serialize(encoder: Encoder, value: CborElement) { + encoder.asCborEncoder() + + // Encode the value + when (value) { + is CborPrimitive<*> -> encoder.encodeSerializableValue(CborPrimitiveSerializer, value) + is CborMap -> encoder.encodeSerializableValue(CborMapSerializer, value) + is CborList -> encoder.encodeSerializableValue(CborListSerializer, value) + } + } + + override fun deserialize(decoder: Decoder): CborElement { + val input = decoder.asCborDecoder() + return input.decodeCborElement() + } +} + +/** + * Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborPrimitive]. + * It can only be used by with [Cbor] format an its input ([CborDecoder] and [CborEncoder]). + */ +internal object CborPrimitiveSerializer : KSerializer> { + override val descriptor: SerialDescriptor = + buildSerialDescriptor("kotlinx.serialization.cbor.CborPrimitive", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: CborPrimitive<*>) { + val cborEncoder = encoder.asCborEncoder() + + cborEncoder.encodeTags(value) + + when (value) { + is CborNull -> encoder.encodeSerializableValue(CborNullSerializer, value) + is CborString -> encoder.encodeSerializableValue(CborStringSerializer, value) + is CborBoolean -> encoder.encodeSerializableValue(CborBooleanSerializer, value) + is CborByteString -> encoder.encodeSerializableValue(CborByteStringSerializer, value) + is CborDouble -> encoder.encodeSerializableValue(CborDoubleSerializer, value) + is CborNegativeInt -> encoder.encodeSerializableValue(CborIntSerializer, value) + is CborPositiveInt -> encoder.encodeSerializableValue(CborUIntSerializer, value) + } + } + + override fun deserialize(decoder: Decoder): CborPrimitive<*> { + val result = decoder.asCborDecoder().decodeCborElement() + if (result !is CborPrimitive<*>) throw CborDecodingException("Unexpected CBOR element, expected CborPrimitive, had ${result::class}") + return result + } +} + +/** + * Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborNull]. + * It can only be used by with [Cbor] format an its input ([CborDecoder] and [CborEncoder]). + */ +internal object CborNullSerializer : KSerializer { + + override val descriptor: SerialDescriptor = + buildSerialDescriptor("kotlinx.serialization.cbor.CborNull", SerialKind.ENUM) + + override fun serialize(encoder: Encoder, value: CborNull) { + encoder.asCborEncoder().encodeTags(value) + encoder.encodeNull() + } + + override fun deserialize(decoder: Decoder): CborNull { + decoder.asCborDecoder() + if (decoder.decodeNotNullMark()) { + throw CborDecodingException("Expected 'null' literal") + } + decoder.decodeNull() + return CborNull() + } +} + +internal object CborIntSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborInt", PrimitiveKind.LONG) + + override fun serialize(encoder: Encoder, value: CborNegativeInt) { + encoder.asCborEncoder().encodeTags(value) + encoder.encodeLong(value.value) + } + + override fun deserialize(decoder: Decoder): CborNegativeInt { + decoder.asCborDecoder() + return CborNegativeInt(decoder.decodeLong()) + } +} + +internal object CborUIntSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("CborUInt", PrimitiveKind.LONG) + + override fun serialize(encoder: Encoder, value: CborPositiveInt) { + encoder.asCborEncoder().encodeTags(value) + encoder.encodeInline(descriptor).encodeSerializableValue(ULong.serializer(), value.value) + } + + override fun deserialize(decoder: Decoder): CborPositiveInt { + decoder.asCborDecoder() + return CborPositiveInt(decoder.decodeInline(descriptor).decodeSerializableValue(ULong.serializer())) + } +} + +internal object CborDoubleSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborDouble", PrimitiveKind.DOUBLE) + + override fun serialize(encoder: Encoder, value: CborDouble) { + encoder.asCborEncoder().encodeTags(value) + encoder.encodeDouble(value.value) + } + + override fun deserialize(decoder: Decoder): CborDouble { + decoder.asCborDecoder() + return CborDouble(decoder.decodeDouble()) + } +} + +/** + * Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborString]. + * It can only be used by with [Cbor] format an its input ([CborDecoder] and [CborEncoder]). + */ +internal object CborStringSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborString", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: CborString) { + encoder.asCborEncoder().encodeTags(value) + encoder.encodeString(value.value) + } + + override fun deserialize(decoder: Decoder): CborString { + val cborDecoder = decoder.asCborDecoder() + val element = cborDecoder.decodeCborElement() + if (element !is CborString) throw CborDecodingException("Unexpected CBOR element, expected CborString, had ${element::class}") + return element + } +} + +/** + * Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborBoolean]. + * It can only be used by with [Cbor] format an its input ([CborDecoder] and [CborEncoder]). + */ +internal object CborBooleanSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborBoolean", PrimitiveKind.BOOLEAN) + + override fun serialize(encoder: Encoder, value: CborBoolean) { + encoder.asCborEncoder().encodeTags(value) + encoder.encodeBoolean(value.value) + } + + override fun deserialize(decoder: Decoder): CborBoolean { + val cborDecoder = decoder.asCborDecoder() + val element = cborDecoder.decodeCborElement() + if (element !is CborBoolean) throw CborDecodingException("Unexpected CBOR element, expected CborBoolean, had ${element::class}") + return element + } +} + +/** + * Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborByteString]. + * It can only be used by with [Cbor] format and its input ([CborDecoder] and [CborEncoder]). + */ +internal object CborByteStringSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborByteString", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: CborByteString) { + val cborEncoder = encoder.asCborEncoder() + cborEncoder.encodeTags(value) + cborEncoder.encodeByteString(value.value) + } + + override fun deserialize(decoder: Decoder): CborByteString { + val cborDecoder = decoder.asCborDecoder() + val element = cborDecoder.decodeCborElement() + if (element !is CborByteString) throw CborDecodingException("Unexpected CBOR element, expected CborByteString, had ${element::class}") + return element + } +} + +/** + * Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborMap]. + * It can only be used by with [Cbor] format and its input ([CborDecoder] and [CborEncoder]). + */ +internal object CborMapSerializer : KSerializer { + private object CborMapDescriptor : + SerialDescriptor by MapSerializer(CborElementSerializer, CborElementSerializer).descriptor { + @ExperimentalSerializationApi + override val serialName: String = "kotlinx.serialization.cbor.CborMap" + } + + override val descriptor: SerialDescriptor = CborMapDescriptor + + override fun serialize(encoder: Encoder, value: CborMap) { + val cborEncoder = encoder.asCborEncoder() + cborEncoder.encodeTags(value) + MapSerializer(CborElementSerializer, CborElementSerializer).serialize(encoder, value) + } + + override fun deserialize(decoder: Decoder): CborMap { + decoder.asCborDecoder() + return CborMap(MapSerializer(CborElementSerializer, CborElementSerializer).deserialize(decoder)) + } +} + +/** + * Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [CborList]. + * It can only be used by with [Cbor] format an its input ([CborDecoder] and [CborEncoder]). + */ +internal object CborListSerializer : KSerializer { + private object CborListDescriptor : SerialDescriptor by ListSerializer(CborElementSerializer).descriptor { + @ExperimentalSerializationApi + override val serialName: String = "kotlinx.serialization.cbor.CborList" + } + + override val descriptor: SerialDescriptor = CborListDescriptor + + override fun serialize(encoder: Encoder, value: CborList) { + val cborEncoder = encoder.asCborEncoder() + cborEncoder.encodeTags(value) + ListSerializer(CborElementSerializer).serialize(encoder, value) + } + + override fun deserialize(decoder: Decoder): CborList { + decoder.asCborDecoder() + return CborList(ListSerializer(CborElementSerializer).deserialize(decoder)) + } +} + + +internal fun Decoder.asCborDecoder(): CborDecoder = this as? CborDecoder + ?: throw IllegalStateException( + "This serializer can be used only with Cbor format." + + "Expected Decoder to be CborDecoder, got ${this::class}" + ) + +/*need to expose writer to access encodeTag()*/ +internal fun Encoder.asCborEncoder() = this as? CborWriter + ?: throw IllegalStateException( + "This serializer can be used only with Cbor format." + + "Expected Encoder to be CborEncoder, got ${this::class}" + ) + +/** + * Returns serial descriptor that delegates all the calls to descriptor returned by [deferred] block. + * Used to resolve cyclic dependencies between recursive serializable structures. + */ +@OptIn(ExperimentalSerializationApi::class) +private fun defer(deferred: () -> SerialDescriptor): SerialDescriptor = object : SerialDescriptor { + private val original: SerialDescriptor by lazy(deferred) + + override val serialName: String + get() = original.serialName + override val kind: SerialKind + get() = original.kind + override val elementsCount: Int + get() = original.elementsCount + + override fun getElementName(index: Int): String = original.getElementName(index) + override fun getElementIndex(name: String): Int = original.getElementIndex(name) + override fun getElementAnnotations(index: Int): List = original.getElementAnnotations(index) + override fun getElementDescriptor(index: Int): SerialDescriptor = original.getElementDescriptor(index) + override fun isElementOptional(index: Int): Boolean = original.isElementOptional(index) +} + +private fun CborWriter.encodeTags(value: CborElement) { // Encode tags if present + if (value.tags.isNotEmpty()) { + for (tag in value.tags) { + encodeTag(tag) + } + } + +} \ No newline at end of file diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborParserInterface.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborParserInterface.kt new file mode 100644 index 0000000000..14296e519a --- /dev/null +++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborParserInterface.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2017-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +@file:OptIn(ExperimentalSerializationApi::class, ExperimentalUnsignedTypes::class) + +package kotlinx.serialization.cbor.internal + +import kotlinx.serialization.* + +/** + * Common interface for CBOR parsers that can read CBOR data from different sources. + */ +internal sealed interface CborParserInterface { + // Basic state checks + fun isNull(): Boolean + fun isEnd(): Boolean + fun end() + + // Collection operations + fun startArray(tags: ULongArray? = null): Int + fun startMap(tags: ULongArray? = null): Int + + // Value reading operations + fun nextNull(tags: ULongArray? = null): Nothing? + fun nextBoolean(tags: ULongArray? = null): Boolean + fun nextNumber(tags: ULongArray? = null): Long + fun nextString(tags: ULongArray? = null): String + fun nextByteString(tags: ULongArray? = null): ByteArray + fun nextDouble(tags: ULongArray? = null): Double + fun nextFloat(tags: ULongArray? = null): Float + + // Map key operations + fun nextTaggedStringOrNumber(): Triple + + // Skip operations + fun skipElement(tags: ULongArray?) + + // Tag verification + fun verifyTagsAndThrow(expected: ULongArray, actual: ULongArray?) + + // Additional methods needed for CborTreeReader + fun nextTag(): ULong +} \ No newline at end of file diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborTreeReader.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborTreeReader.kt new file mode 100644 index 0000000000..c5fe746454 --- /dev/null +++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborTreeReader.kt @@ -0,0 +1,145 @@ +/* + * Copyright 2017-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +@file:OptIn(ExperimentalSerializationApi::class, ExperimentalUnsignedTypes::class) + +package kotlinx.serialization.cbor.internal + +import kotlinx.serialization.* +import kotlinx.serialization.cbor.* + +/** + * [CborTreeReader] reads CBOR data from [parser] and constructs a [CborElement] tree. + */ +internal class CborTreeReader( + //no config values make sense here, because we have no "schema". + //we cannot validate tags, or disregard nulls, can we?! + //still, this needs to go here, in case it evolves to a point where we need to respect certain config values + private val configuration: CborConfiguration, + private val parser: CborParser +) { + /** + * Reads the next CBOR element from the parser. + */ + fun read(): CborElement { + // Read any tags before the actual value + val tags = readTags() + + val result = when (parser.curByte shr 5) { // Get major type from the first 3 bits + 0 -> { // Major type 0: unsigned integer + val value = parser.nextNumber() + CborPositiveInt(value.toULong(), tags) + } + + 1 -> { // Major type 1: negative integer + val value = parser.nextNumber() + CborNegativeInt(value, tags) + } + + 2 -> { // Major type 2: byte string + CborByteString(parser.nextByteString(), tags) + } + + 3 -> { // Major type 3: text string + CborString(parser.nextString(), tags) + } + + 4 -> { // Major type 4: array + readArray(tags) + } + + 5 -> { // Major type 5: map + readMap(tags) + } + + 7 -> { // Major type 7: simple/float/break + when (parser.curByte) { + 0xF4 -> { + parser.readByte() // Advance parser position + CborBoolean(false, tags) + } + + 0xF5 -> { + parser.readByte() // Advance parser position + CborBoolean(true, tags) + } + + 0xF6, 0xF7 -> { + parser.nextNull() + CborNull(tags) + } + // Half/Float32/Float64 + NEXT_HALF, NEXT_FLOAT, NEXT_DOUBLE -> CborDouble(parser.nextDouble(), tags) + else -> throw CborDecodingException( + "Invalid simple value or float type: ${parser.curByte.toString(16)}" + ) + } + } + + else -> throw CborDecodingException("Invalid CBOR major type: ${parser.curByte shr 5}") + } + return result + } + + /** + * Reads any tags preceding the current value. + * @return An array of tags, possibly empty + */ + @OptIn(ExperimentalUnsignedTypes::class) + private fun readTags(): ULongArray { + val tags = mutableListOf() + + // Read tags (major type 6) until we encounter a non-tag + while ((parser.curByte shr 5) == 6) { // Major type 6: tag + val tag = parser.nextTag() + tags.add(tag) + } + + return tags.toULongArray() + } + + + private fun readArray(tags: ULongArray): CborList { + val size = parser.startArray() + val elements = mutableListOf() + + if (size >= 0) { + // Definite length array + repeat(size) { + elements.add(read()) + } + } else { + // Indefinite length array + while (!parser.isEnd()) { + elements.add(read()) + } + parser.end() + } + + return CborList(elements, tags) + } + + private fun readMap(tags: ULongArray): CborMap { + val size = parser.startMap() + val elements = mutableMapOf() + + if (size >= 0) { + // Definite length map + repeat(size) { + val key = read() + val value = read() + elements[key] = value + } + } else { + // Indefinite length map + while (!parser.isEnd()) { + val key = read() + val value = read() + elements[key] = value + } + parser.end() + } + + return CborMap(elements, tags) + } +} diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Decoder.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Decoder.kt index 88075db26f..ef4e818901 100644 --- a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Decoder.kt +++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Decoder.kt @@ -12,9 +12,16 @@ import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* import kotlinx.serialization.modules.* -internal open class CborReader(override val cbor: Cbor, protected val parser: CborParser) : AbstractDecoder(), +internal open class CborReader(override val cbor: Cbor, protected val parser: CborParserInterface) : AbstractDecoder(), CborDecoder { + override fun decodeCborElement(): CborElement = + when(parser) { + is CborParser -> CborTreeReader(cbor.configuration, parser).read() + is StructuredCborParser -> parser.element + } + + protected var size = -1 private set protected var finiteMode = false @@ -151,14 +158,14 @@ internal open class CborReader(override val cbor: Cbor, protected val parser: Cb } } -internal class CborParser(private val input: ByteArrayInput, private val verifyObjectTags: Boolean) { - private var curByte: Int = -1 +internal class CborParser(private val input: ByteArrayInput, private val verifyObjectTags: Boolean) : CborParserInterface { + var curByte: Int = -1 init { readByte() } - private fun readByte(): Int { + fun readByte(): Int { curByte = input.read() return curByte } @@ -170,9 +177,35 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO readByte() } - fun isNull() = (curByte == NULL || curByte == EMPTY_MAP) + override fun isNull() = (curByte == NULL || curByte == EMPTY_MAP || curByte == -1) + + // Add this method to CborParser class + private fun readUnsignedValueFromAdditionalInfo(additionalInfo: Int): Long { + return when (additionalInfo) { + in 0..23 -> additionalInfo.toLong() + 24 -> { + val nextByte = readByte() + if (nextByte == -1) throw CborDecodingException("Unexpected EOF") + nextByte.toLong() and 0xFF + } + + 25 -> input.readExact(2) + 26 -> input.readExact(4) + 27 -> input.readExact(8) + else -> throw CborDecodingException("Invalid additional info: $additionalInfo") + } + } + + override fun nextTag(): ULong { + if ((curByte shr 5) != 6) { + throw CborDecodingException("Expected tag (major type 6), got major type ${curByte shr 5}") + } + + val additionalInfo = curByte and 0x1F + return readUnsignedValueFromAdditionalInfo(additionalInfo).toULong().also { skipByte(curByte) } + } - fun nextNull(tags: ULongArray? = null): Nothing? { + override fun nextNull(tags: ULongArray?): Nothing? { processTags(tags) if (curByte == NULL) { skipByte(NULL) @@ -182,7 +215,7 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO return null } - fun nextBoolean(tags: ULongArray? = null): Boolean { + override fun nextBoolean(tags: ULongArray?): Boolean { processTags(tags) val ans = when (curByte) { TRUE -> true @@ -193,9 +226,9 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO return ans } - fun startArray(tags: ULongArray? = null) = startSized(tags, BEGIN_ARRAY, HEADER_ARRAY, "array") + override fun startArray(tags: ULongArray?) = startSized(tags, BEGIN_ARRAY, HEADER_ARRAY, "array") - fun startMap(tags: ULongArray? = null) = startSized(tags, BEGIN_MAP, HEADER_MAP, "map") + override fun startMap(tags: ULongArray?) = startSized(tags, BEGIN_MAP, HEADER_MAP, "map") private fun startSized( tags: ULongArray?, @@ -215,11 +248,11 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO return size } - fun isEnd() = curByte == BREAK + override fun isEnd() = curByte == BREAK - fun end() = skipByte(BREAK) + override fun end() = skipByte(BREAK) - fun nextByteString(tags: ULongArray? = null): ByteArray { + override fun nextByteString(tags: ULongArray?): ByteArray { processTags(tags) if ((curByte and 0b111_00000) != HEADER_BYTE_STRING) throw CborDecodingException("start of byte string", curByte) @@ -228,7 +261,7 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO return arr } - fun nextString(tags: ULongArray? = null) = nextTaggedString(tags).first + override fun nextString(tags: ULongArray?) = nextTaggedString(tags).first //used for reading the tag names and names of tagged keys (of maps, and serialized classes) private fun nextTaggedString(tags: ULongArray?): Pair { @@ -282,7 +315,7 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO } } - internal fun verifyTagsAndThrow(expected: ULongArray, actual: ULongArray?) { + override fun verifyTagsAndThrow(expected: ULongArray, actual: ULongArray?) { if (!expected.contentEquals(actual)) throw CborDecodingException( "CBOR tags ${actual?.contentToString()} do not match expected tags ${expected.contentToString()}" @@ -292,7 +325,7 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO /** * Used for reading the tags and either string (element name) or number (serial label) */ - fun nextTaggedStringOrNumber(): Triple { + override fun nextTaggedStringOrNumber(): Triple { val collectedTags = processTags(null) if ((curByte and 0b111_00000) == HEADER_STRING) { val arr = readBytes() @@ -306,7 +339,8 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO } } - fun nextNumber(tags: ULongArray? = null): Long { + + override fun nextNumber(tags: ULongArray?): Long { processTags(tags) val res = readNumber() readByte() @@ -314,22 +348,11 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO } private fun readNumber(): Long { - val value = curByte and 0b000_11111 + val additionalInfo = curByte and 0b000_11111 val negative = (curByte and 0b111_00000) == HEADER_NEGATIVE.toInt() - val bytesToRead = when (value) { - 24 -> 1 - 25 -> 2 - 26 -> 4 - 27 -> 8 - else -> 0 - } - if (bytesToRead == 0) { - return if (negative) -(value + 1).toLong() - else value.toLong() - } - val res = input.readExact(bytesToRead) - return if (negative) -(res + 1) - else res + + val value = readUnsignedValueFromAdditionalInfo(additionalInfo) + return if (negative) -(value + 1) else value } private fun ByteArrayInput.readExact(bytes: Int): Long { @@ -350,7 +373,7 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO return array } - fun nextFloat(tags: ULongArray? = null): Float { + override fun nextFloat(tags: ULongArray?): Float { processTags(tags) val res = when (curByte) { NEXT_FLOAT -> Float.fromBits(readInt()) @@ -361,7 +384,7 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO return res } - fun nextDouble(tags: ULongArray? = null): Double { + override fun nextDouble(tags: ULongArray?): Double { processTags(tags) val res = when (curByte) { NEXT_DOUBLE -> Double.fromBits(readLong()) @@ -409,7 +432,7 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO * been skipped, the "length stack" is [pruned][prune]. For indefinite length elements, a special marker is added to * the "length stack" which is only popped from the "length stack" when a CBOR [break][isEnd] is encountered. */ - fun skipElement(tags: ULongArray?) { + override fun skipElement(tags: ULongArray?) { val lengthStack = mutableListOf() processTags(tags) @@ -531,14 +554,179 @@ private fun Iterable.flatten(): ByteArray { return output } +private typealias ElementHolder = Pair, CborElement> +private val ElementHolder.tags: MutableList get() = first +private val ElementHolder.element: CborElement get() = second +internal class StructuredCborParser(val element: CborElement, private val verifyObjectTags: Boolean) : CborParserInterface { + + + internal var current: ElementHolder = element.tags.toMutableList() to element + private var listIterator: Iterator? = null -private class CborMapReader(cbor: Cbor, decoder: CborParser) : CborListReader(cbor, decoder) { + // Implementation of methods needed for CborTreeReader + override fun nextTag(): ULong { + if (current.tags.isEmpty()) { + throw CborDecodingException("Expected tag, but no tags found on current element") + } + return current.tags.removeFirst() + } + + override fun isNull() : Boolean { + //TODO this is a bit wonky! if we are inside a map, we want to skip over the key, and check the value, + // so the below call is not what it should be! + processTags(null) + return current.element is CborNull + } + + override fun isEnd() = when { + listIterator != null -> !listIterator!!.hasNext() + else -> false + } + + override fun end() { + // Reset iterators when ending a structure + listIterator = null + } + + override fun startArray(tags: ULongArray?): Int { + processTags(tags) + if (current.element !is CborList) { + throw CborDecodingException("Expected array, got ${current.element::class.simpleName}") + } + + val list = current.element as CborList + listIterator = list.iterator() + return list.size + } + + override fun startMap(tags: ULongArray?): Int { + processTags(tags) + if (current.element !is CborMap) { + throw CborDecodingException("Expected map, got ${current.element::class.simpleName}") + } + + val map = current.element as CborMap + //zip key, value, key, value, ... pairs to mirror byte-layout of CBOR map + listIterator = map.entries.flatMap { listOf(it.key, it.value) }.iterator() + return map.size //cbor map size is the size of the map, not the doubled size of the flattened pairs + } + + override fun nextNull(tags: ULongArray?): Nothing? { + processTags(tags) + if (current.element !is CborNull) { + throw CborDecodingException("Expected null, got ${current.element::class.simpleName}") + } + return null + } + + override fun nextBoolean(tags: ULongArray?): Boolean { + processTags(tags) + if (current.element !is CborBoolean) { + throw CborDecodingException("Expected boolean, got ${current.element::class.simpleName}") + } + return (current.element as CborBoolean).value + } + + override fun nextNumber(tags: ULongArray?): Long { + processTags(tags) + return when (current.element) { + is CborPositiveInt -> (current.element as CborPositiveInt).value.toLong() + is CborNegativeInt -> (current.element as CborNegativeInt).value + else -> throw CborDecodingException("Expected number, got ${current.element::class.simpleName}") + } + } + + override fun nextString(tags: ULongArray?): String { + processTags(tags) + + // Special handling for polymorphic serialization + // If we have a CborList with a string as first element, return that string + if (current.element is CborList && (current.element as CborList).isNotEmpty() && (current.element as CborList)[0] is CborString) { + val stringElement = (current.element as CborList)[0] as CborString + // Move to the next element (the map) for subsequent operations + current = (current.element as CborList)[1].tags.toMutableList() to (current.element as CborList)[1] + return stringElement.value + } + + if (current.element !is CborString) { + throw CborDecodingException("Expected string, got ${current.element::class.simpleName}") + } + return (current.element as CborString).value + } + + override fun nextByteString(tags: ULongArray?): ByteArray { + processTags(tags) + if (current.element !is CborByteString) { + throw CborDecodingException("Expected byte string, got ${current.element::class.simpleName}") + } + return (current.element as CborByteString).value + } + + override fun nextDouble(tags: ULongArray?): Double { + processTags(tags) + return when (current.element) { + is CborDouble -> (current.element as CborDouble).value + else -> throw CborDecodingException("Expected double, got ${current.element::class.simpleName}") + } + } + + override fun nextFloat(tags: ULongArray?): Float { + return nextDouble(tags).toFloat() + } + + override fun nextTaggedStringOrNumber(): Triple { + val tags = processTags(null) + + return when (val key = current.element) { + is CborString -> Triple(key.value, null, tags) + is CborPositiveInt -> Triple(null, key.value.toLong(), tags) + is CborNegativeInt -> Triple(null, key.value, tags) + else -> throw CborDecodingException("Expected string or number key, got ${key?.let { it::class.simpleName } ?: "null"}") + } + } + + private fun processTags(tags: ULongArray?): ULongArray? { + + // If we're in a list, advance to the next element + if (listIterator != null && listIterator!!.hasNext()) { + listIterator!!.next().let { current = it.tags.toMutableList() to it } + } + + // Store collected tags for verification + val collectedTags = if (current.tags.isEmpty()) null else current.tags.toULongArray() + + // Verify tags if needed + if (verifyObjectTags) { + tags?.let { + verifyTagsAndThrow(it, collectedTags) + } + } + + return collectedTags + } + + override fun verifyTagsAndThrow(expected: ULongArray, actual: ULongArray?) { + if (!expected.contentEquals(actual)) { + throw CborDecodingException( + "CBOR tags ${actual?.contentToString()} do not match expected tags ${expected.contentToString()}" + ) + } + } + + override fun skipElement(tags: ULongArray?) { + // Process tags but don't do anything with the element + processTags(tags) + } +} + + +private class CborMapReader(cbor: Cbor, decoder: CborParserInterface) : CborListReader(cbor, decoder) { override fun skipBeginToken(objectTags: ULongArray?) = setSize(parser.startMap(tags?.let { if (objectTags == null) it else ulongArrayOf(*it, *objectTags) } - ?: objectTags) * 2) + ?: objectTags)) } -private open class CborListReader(cbor: Cbor, decoder: CborParser) : CborReader(cbor, decoder) { +private open class CborListReader(cbor: Cbor, decoder: CborParserInterface) : CborReader(cbor, decoder) { private var ind = 0 override fun skipBeginToken(objectTags: ULongArray?) = @@ -553,7 +741,6 @@ private open class CborListReader(cbor: Cbor, decoder: CborParser) : CborReader( } } - private val normalizeBaseBits = SINGLE_PRECISION_NORMALIZE_BASE.toBits() @@ -617,4 +804,4 @@ private fun SerialDescriptor.getElementIndexOrThrow(name: String): Int { " You can enable 'CborBuilder.ignoreUnknownKeys' property to ignore unknown keys" ) return index -} +} \ No newline at end of file diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoder.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoder.kt index eb5fc556a2..94a369c3a4 100644 --- a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoder.kt +++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoder.kt @@ -26,15 +26,19 @@ private fun Stack.peek() = last() // Split implementation to optimize base case internal sealed class CborWriter( override val cbor: Cbor, - protected val output: ByteArrayOutput, ) : AbstractEncoder(), CborEncoder { + + internal open fun encodeByteString(byteArray: ByteArray) { + getDestination().encodeByteString(byteArray) + } + protected var isClass = false protected var encodeByteArrayAsByteString = false class Data(val bytes: ByteArrayOutput, var elementCount: Int) - protected abstract fun getDestination(): ByteArrayOutput + internal abstract fun getDestination(): ByteArrayOutput override val serializersModule: SerializersModule get() = cbor.serializersModule @@ -46,7 +50,7 @@ internal sealed class CborWriter( if ((encodeByteArrayAsByteString || cbor.configuration.alwaysUseByteString) && serializer.descriptor == ByteArraySerializer().descriptor ) { - getDestination().encodeByteString(value as ByteArray) + encodeByteString(value as ByteArray) } else { encodeByteArrayAsByteString = encodeByteArrayAsByteString || serializer.descriptor.isInlineByteString() super.encodeSerializableValue(serializer, value) @@ -143,12 +147,14 @@ internal sealed class CborWriter( incrementChildren() // needed for definite len encoding, NOOP for indefinite length encoding return true } + + internal fun encodeTag(tag: ULong) = getDestination().encodeTag(tag) } // optimized indefinite length encoder -internal class IndefiniteLengthCborWriter(cbor: Cbor, output: ByteArrayOutput) : CborWriter( - cbor, output +internal class IndefiniteLengthCborWriter(cbor: Cbor, private val output: ByteArrayOutput) : CborWriter( + cbor ) { override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder { @@ -179,8 +185,167 @@ internal class IndefiniteLengthCborWriter(cbor: Cbor, output: ByteArrayOutput) : } +// optimized indefinite length encoder +internal class StructuredCborWriter(cbor: Cbor) : CborWriter( + cbor +) { + + sealed class CborContainer(tags: ULongArray, elements: MutableList) { + var elements = elements + private set + + var tags = tags + internal set + + + open fun add(element: CborElement) = elements.add(element) + class Map(tags: ULongArray, elements: MutableList = mutableListOf()) : + CborContainer(tags, elements) + + class List(tags: ULongArray, elements: MutableList = mutableListOf()) : + CborContainer(tags, elements) + + class Primitive(tags: ULongArray) : CborContainer(tags, elements = mutableListOf()) { + override fun add(element: CborElement): Boolean { + require(elements.isEmpty()) {"Implementation error. Please report a bug."} + return elements.add(element) + } + } + + fun finalize() = when (this) { + is List -> CborList(content = elements, tags = tags) + is Map -> CborMap( + content = if (elements.isNotEmpty()) IntRange(0, elements.size / 2 - 1).associate { + elements[it * 2] to elements[it * 2 + 1] + } else mapOf(), + tags = tags + ) + + is Primitive -> elements.first().also { it.tags += tags } + + } + } + + private val stack = ArrayDeque() + private var currentElement: CborContainer? = null + + fun finalize() = currentElement!!.finalize() + + override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder { + //TODO check if cborelement and be done + val tags = descriptor.getObjectTags() ?: ulongArrayOf() + val element = if (descriptor.hasArrayTag()) { + CborContainer.List(tags) + } else { + when (descriptor.kind) { + StructureKind.LIST, is PolymorphicKind -> CborContainer.List(tags) + is StructureKind.MAP -> CborContainer.Map(tags) + else -> CborContainer.Map(tags) + } + } + currentElement?.let { stack.add(it) } + currentElement = element + return this + } + + override fun endStructure(descriptor: SerialDescriptor) { + val finalized = currentElement!!.finalize() + if (stack.isNotEmpty()) { + currentElement = stack.removeLast() + currentElement!!.add(finalized) + } + } + + override fun getDestination() = throw IllegalStateException("There is not byteArrayOutput") + + + override fun incrementChildren() {/*NOOP*/ + } + + + override fun encodeElement(descriptor: SerialDescriptor, index: Int): Boolean { + //we don't care for special encoding of an empty class, so we don't set this flag here + // isClass = descriptor.getElementDescriptor(index).kind == StructureKind.CLASS + encodeByteArrayAsByteString = descriptor.isByteString(index) + //TODO check if cborelement and be done + val name = descriptor.getElementName(index) + if (!descriptor.hasArrayTag()) { + val keyTags = descriptor.getKeyTags(index) + + if ((descriptor.kind !is StructureKind.LIST) && (descriptor.kind !is StructureKind.MAP) && (descriptor.kind !is PolymorphicKind)) { + //indices are put into the name field. we don't want to write those, as it would result in double writes + val cborLabel = descriptor.getCborLabel(index) + if (cbor.configuration.preferCborLabelsOverNames && cborLabel != null) { + currentElement!!.add( + CborInt.invoke(value = cborLabel, tags = keyTags ?: ulongArrayOf()) + ) + } else { + currentElement!!.add(CborString(name, keyTags ?: ulongArrayOf())) + } + } + } + + if (cbor.configuration.encodeValueTags) { + descriptor.getValueTags(index)?.let { valueTags -> + currentElement!!.tags += valueTags + } + } + return true + } + + + override fun encodeBoolean(value: Boolean) { + currentElement!!.add(CborBoolean(value)) + } + + override fun encodeByte(value: Byte) { + currentElement!!.add(CborInt(value.toLong())) + } + + override fun encodeChar(value: Char) { + currentElement!!.add(CborInt(value.code.toLong())) + } + + override fun encodeDouble(value: Double) { + currentElement!!.add(CborDouble(value)) + } + + override fun encodeFloat(value: Float) { + currentElement!!.add(CborDouble(value.toDouble())) + } + + override fun encodeInt(value: Int) { + currentElement!!.add(CborInt(value.toLong())) + } + + override fun encodeLong(value: Long) { + currentElement!!.add(CborInt(value)) + } + + override fun encodeShort(value: Short) { + currentElement!!.add(CborInt(value.toLong())) + } + + override fun encodeString(value: String) { + currentElement!!.add(CborString(value)) + } + + override fun encodeByteString(byteArray: ByteArray) { + currentElement!!.add(CborByteString(byteArray)) + } + + override fun encodeNull() { + currentElement!!.add(CborNull()) + } + + override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) { + currentElement!!.add(CborString(enumDescriptor.getElementName(index))) + } + +} + //optimized definite length encoder -internal class DefiniteLengthCborWriter(cbor: Cbor, output: ByteArrayOutput) : CborWriter(cbor, output) { +internal class DefiniteLengthCborWriter(cbor: Cbor, output: ByteArrayOutput) : CborWriter(cbor) { private val structureStack = Stack(Data(output, -1)) override fun getDestination(): ByteArrayOutput = @@ -234,7 +399,7 @@ private fun ByteArrayOutput.startMap(size: ULong) { composePositiveInline(size, HEADER_MAP) } -private fun ByteArrayOutput.encodeTag(tag: ULong) { +internal fun ByteArrayOutput.encodeTag(tag: ULong) { composePositiveInline(tag, HEADER_TAG) } @@ -329,4 +494,3 @@ private fun composeNegative(value: Long): ByteArray { data[0] = data[0] or HEADER_NEGATIVE return data } - diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborDecoderTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborDecoderTest.kt index 92aee674be..c1d4d528b5 100644 --- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborDecoderTest.kt +++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborDecoderTest.kt @@ -17,7 +17,10 @@ class CborDecoderTest { @Test fun testDecodeSimpleObject() { - assertEquals(Simple("str"), Cbor.decodeFromHexString(Simple.serializer(), "bf616163737472ff")) + val hex = "bf616163737472ff" + assertEquals(Simple("str"), Cbor.decodeFromHexString(Simple.serializer(), hex)) + val struct = Cbor.decodeFromHexString(hex) + assertEquals(Simple("str"), Cbor.decodeFromCbor(Simple.serializer(), struct)) } @Test @@ -34,12 +37,19 @@ class CborDecoderTest { HexConverter.parseHexBinary("cafe") ) // with maps, lists & strings of indefinite length + val hex = + "bf637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973749f61616162ff636d6170bf01f502f4ff65696e6e6572bf6161636c6f6cff6a696e6e6572734c6973749fbf6161636b656bffff6a62797465537472696e675f42cafeff696279746541727261799f383521ffff" assertEquals( test, Cbor.decodeFromHexString( TypesUmbrella.serializer(), - "bf637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973749f61616162ff636d6170bf01f502f4ff65696e6e6572bf6161636c6f6cff6a696e6e6572734c6973749fbf6161636b656bffff6a62797465537472696e675f42cafeff696279746541727261799f383521ffff" + hex ) ) + + val struct = Cbor.decodeFromHexString(hex) + assertEquals(test, Cbor.decodeFromCbor(TypesUmbrella.serializer(), struct)) + + // with maps, lists & strings of definite length assertEquals( test, Cbor.decodeFromHexString( @@ -57,31 +67,43 @@ class CborDecoderTest { * 44 # bytes(4) * 01020304 # "\x01\x02\x03\x04" */ + val hex = "a16a62797465537472696e674401020304" + val expected = NullableByteString(byteArrayOf(1, 2, 3, 4)) assertEquals( - expected = NullableByteString(byteArrayOf(1, 2, 3, 4)), + expected = expected, actual = Cbor.decodeFromHexString( deserializer = NullableByteString.serializer(), - hex = "a16a62797465537472696e674401020304" + hex = hex ) ) + val struct = Cbor.decodeFromHexString(hex) + assertEquals(expected, Cbor.decodeFromCbor(NullableByteString.serializer(), struct)) + /* A1 # map(1) * 6A # text(10) * 62797465537472696E67 # "byteString" * F6 # primitive(22) */ + val hexNull = "a16a62797465537472696e67f6" + val expectedNull = NullableByteString(byteString = null) assertEquals( - expected = NullableByteString(byteString = null), + expected = expectedNull, actual = Cbor.decodeFromHexString( deserializer = NullableByteString.serializer(), - hex = "a16a62797465537472696e67f6" + hex = hexNull ) ) + + val structNull = Cbor.decodeFromHexString(hexNull) + assertEquals(expectedNull, Cbor.decodeFromCbor(NullableByteString.serializer(), structNull)) } @Test fun testNullables() { Cbor.decodeFromHexString("a0") + val struct = Cbor.decodeFromHexString("a0") + assertEquals(NullableByteStringDefaultNull(), Cbor.decodeFromCbor(NullableByteStringDefaultNull.serializer(), struct)) } /** @@ -171,8 +193,10 @@ class CborDecoderTest { @Test fun testDecodeCborWithUnknownField() { + val hex = "bf616163313233616263393837ff" + val expected = Simple("123") assertEquals( - expected = Simple("123"), + expected = expected, actual = ignoreUnknownKeys.decodeFromHexString( deserializer = Simple.serializer(), @@ -187,15 +211,20 @@ class CborDecoderTest { * 393837 # "987" * FF # primitive(*) */ - hex = "bf616163313233616263393837ff" + hex = hex ) ) + val struct = Cbor.decodeFromHexString(hex) + assertEquals(expected, ignoreUnknownKeys.decodeFromCbor(Simple.serializer(), struct)) + } @Test fun testDecodeCborWithUnknownNestedIndefiniteFields() { + val hex = "bf6161633132336162bf7f6178ffa161790aff61639f010203ffff" + val expected = Simple("123") assertEquals( - expected = Simple("123"), + expected = expected, actual = ignoreUnknownKeys.decodeFromHexString( deserializer = Simple.serializer(), @@ -225,9 +254,12 @@ class CborDecoderTest { * FF # primitive(*) * FF # primitive(*) */ - hex = "bf6161633132336162bf7f6178ffa161790aff61639f010203ffff" + hex = hex ) ) + + val struct = Cbor.decodeFromHexString(hex) + assertEquals(expected, ignoreUnknownKeys.decodeFromCbor(Simple.serializer(), struct)) } /** @@ -308,70 +340,107 @@ class CborDecoderTest { * FF # primitive(*) */ + val expected = SealedBox( + listOf( + SubSealedA("a"), + SubSealedB(1) + ) + ) + val hex = + "bf6565787472618309080765626f7865649f9f782d6b6f746c696e782e73657269616c697a6174696f6e2e53696d706c655365616c65642e5375625365616c656441bf61736161646e657741bf617801617902ffffff9f782d6b6f746c696e782e73657269616c697a6174696f6e2e53696d706c655365616c65642e5375625365616c656442bf616901ffffffff" assertEquals( - expected = SealedBox( - listOf( - SubSealedA("a"), - SubSealedB(1) - ) - ), + expected = expected, actual = ignoreUnknownKeys.decodeFromHexString( SealedBox.serializer(), - "bf6565787472618309080765626f7865649f9f782d6b6f746c696e782e73657269616c697a6174696f6e2e53696d706c655365616c65642e5375625365616c656441bf61736161646e657741bf617801617902ffffff9f782d6b6f746c696e782e73657269616c697a6174696f6e2e53696d706c655365616c65642e5375625365616c656442bf616901ffffffff" + hex ) ) + val ref = ignoreUnknownKeys.encodeToCbor(expected) + val struct = Cbor.decodeFromHexString(hex) + assertEquals(expected, ignoreUnknownKeys.decodeFromCbor(SealedBox.serializer(), struct)) + } @Test fun testReadCustomByteString() { + val expected = TypeWithCustomByteString(CustomByteString(0x11, 0x22, 0x33)) + val hex = "bf617843112233ff" assertEquals( - expected = TypeWithCustomByteString(CustomByteString(0x11, 0x22, 0x33)), - actual = Cbor.decodeFromHexString("bf617843112233ff") + expected = expected, + actual = Cbor.decodeFromHexString(hex) ) + val struct = Cbor.decodeFromHexString(hex) + assertEquals(expected, Cbor.decodeFromCbor(TypeWithCustomByteString.serializer(), struct)) + } @Test fun testReadNullableCustomByteString() { + val hex = "bf617843112233ff" + val expected = TypeWithNullableCustomByteString(CustomByteString(0x11, 0x22, 0x33)) assertEquals( - expected = TypeWithNullableCustomByteString(CustomByteString(0x11, 0x22, 0x33)), - actual = Cbor.decodeFromHexString("bf617843112233ff") + expected = expected, + actual = Cbor.decodeFromHexString(hex) ) + val struct = Cbor.decodeFromHexString(hex) + assertEquals(expected, Cbor.decodeFromCbor(TypeWithNullableCustomByteString.serializer(), struct)) + } @Test fun testReadNullCustomByteString() { + val hex = "bf6178f6ff" + val expected = TypeWithNullableCustomByteString(null) assertEquals( - expected = TypeWithNullableCustomByteString(null), - actual = Cbor.decodeFromHexString("bf6178f6ff") + expected = expected, + actual = Cbor.decodeFromHexString(hex) ) + val struct = Cbor.decodeFromHexString(hex) + assertEquals(expected, Cbor.decodeFromCbor(TypeWithNullableCustomByteString.serializer(), struct)) + } @Test fun testReadValueClassWithByteString() { + val expected = byteArrayOf(0x11, 0x22, 0x33) + val hex = "43112233" assertContentEquals( - expected = byteArrayOf(0x11, 0x22, 0x33), - actual = Cbor.decodeFromHexString("43112233").x + expected = expected, + actual = Cbor.decodeFromHexString(hex).x ) + val struct = Cbor.decodeFromHexString(hex) + assertContentEquals(expected, Cbor.decodeFromCbor(ValueClassWithByteString.serializer(), struct).x) + } @Test fun testReadValueClassCustomByteString() { + val expected = ValueClassWithCustomByteString(CustomByteString(0x11, 0x22, 0x33)) + val hex = "43112233" assertEquals( - expected = ValueClassWithCustomByteString(CustomByteString(0x11, 0x22, 0x33)), - actual = Cbor.decodeFromHexString("43112233") + expected = expected, + actual = Cbor.decodeFromHexString(hex) ) + val struct = Cbor.decodeFromHexString(hex) + assertEquals(expected, Cbor.decodeFromCbor(ValueClassWithCustomByteString.serializer(), struct)) + } @Test fun testReadValueClassWithUnlabeledByteString() { + val expected = byteArrayOf( + 0x11, + 0x22, + 0x33 + ) + val hex = "43112233" assertContentEquals( - expected = byteArrayOf( - 0x11, - 0x22, - 0x33 - ), - actual = Cbor.decodeFromHexString("43112233").x.x + expected = expected, + actual = Cbor.decodeFromHexString(hex).x.x ) + val struct = Cbor.decodeFromHexString(hex) + assertContentEquals(expected, Cbor.decodeFromCbor(ValueClassWithUnlabeledByteString.serializer(), struct).x.x) + } } diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborElementEqualityTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborElementEqualityTest.kt new file mode 100644 index 0000000000..08868ebc9e --- /dev/null +++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborElementEqualityTest.kt @@ -0,0 +1,291 @@ +/* + * Copyright 2017-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization.cbor + +import kotlin.test.* + +class CborElementEqualityTest { + + @Test + fun testCborPositiveIntEquality() { + val int1 = CborPositiveInt(42u) + val int2 = CborPositiveInt(42u) + val int3 = CborPositiveInt(43u) + val int4 = CborPositiveInt(42u, ulongArrayOf(1u)) + + // Same values should be equal + assertEquals(int1, int2) + assertEquals(int1.hashCode(), int2.hashCode()) + + // Different values should not be equal + assertNotEquals(int1, int3) + + // Different tags should not be equal + assertNotEquals(int1, int4) + + // Null comparison + assertNotEquals(int1, null as CborElement?) + + // Different type comparison + assertNotEquals(int1 as CborElement, CborString("42")) + assertNotEquals(int1, CborString("42") as CborElement) + } + + @Test + fun testCborNegativeIntEquality() { + val int1 = CborNegativeInt(-42) + val int2 = CborNegativeInt(-42) + val int3 = CborNegativeInt(-43) + val int4 = CborNegativeInt(-42, ulongArrayOf(1u)) + + assertEquals(int1, int2) + assertEquals(int1.hashCode(), int2.hashCode()) + assertNotEquals(int1, int3) + assertNotEquals(int1, int4) + assertNotEquals(int1, null as CborElement?) + assertNotEquals(int1, CborPositiveInt(42u) as CborElement) + assertNotEquals(int1 as CborElement, CborPositiveInt(42u)) + } + + @Test + fun testCborDoubleEquality() { + val double1 = CborDouble(3.14) + val double2 = CborDouble(3.14) + val double3 = CborDouble(2.71) + val double4 = CborDouble(3.14, ulongArrayOf(1u)) + + assertEquals(double1, double2) + assertEquals(double1.hashCode(), double2.hashCode()) + assertNotEquals(double1, double3) + assertNotEquals(double1, double4) + assertNotEquals(double1, null as CborElement?) + assertNotEquals(double1 as CborElement, CborString("3.14")) + assertNotEquals(double1, CborString("3.14") as CborElement) + } + + @Test + fun testCborStringEquality() { + val string1 = CborString("hello") + val string2 = CborString("hello") + val string3 = CborString("world") + val string4 = CborString("hello", ulongArrayOf(1u)) + + assertEquals(string1, string2) + assertEquals(string1.hashCode(), string2.hashCode()) + assertNotEquals(string1, string3) + assertNotEquals(string1, string4) + assertNotEquals(string1, null as CborElement?) + assertNotEquals(string1 as CborElement, CborPositiveInt(123u)) + assertNotEquals(string1, CborPositiveInt(123u) as CborElement) + } + + @Test + fun testCborBooleanEquality() { + val bool1 = CborBoolean(true) + val bool2 = CborBoolean(true) + val bool3 = CborBoolean(false) + val bool4 = CborBoolean(true, ulongArrayOf(1u)) + + assertEquals(bool1, bool2) + assertEquals(bool1.hashCode(), bool2.hashCode()) + assertNotEquals(bool1, bool3) + assertNotEquals(bool1, bool4) + assertNotEquals(bool1, null as CborElement?) + assertNotEquals(bool1 as CborElement, CborString("true")) + assertNotEquals(bool1, CborString("true") as CborElement) + } + + @Test + fun testCborByteStringEquality() { + val bytes1 = byteArrayOf(1, 2, 3) + val bytes2 = byteArrayOf(1, 2, 3) + val bytes3 = byteArrayOf(4, 5, 6) + + val byteString1 = CborByteString(bytes1) + val byteString2 = CborByteString(bytes2) + val byteString3 = CborByteString(bytes3) + val byteString4 = CborByteString(bytes1, ulongArrayOf(1u)) + + assertEquals(byteString1, byteString2) + assertEquals(byteString1.hashCode(), byteString2.hashCode()) + assertNotEquals(byteString1, byteString3) + assertNotEquals(byteString1, byteString4) + assertNotEquals(byteString1, null as CborElement?) + assertNotEquals(byteString1 as CborElement, CborString("123")) + assertNotEquals(byteString1, CborString("123") as CborElement) + } + + @Test + fun testCborNullEquality() { + val null1 = CborNull() + val null2 = CborNull() + val null3 = CborNull(ulongArrayOf(1u)) + + assertEquals(null1, null2) + assertEquals(null1.hashCode(), null2.hashCode()) + assertNotEquals(null1, null3) + assertNotEquals(null1, null as CborElement?) + assertNotEquals(null1 as CborElement, CborString("null")) + assertNotEquals(null1, CborString("null") as CborElement) + } + + @Test + fun testCborListEquality() { + val list1 = CborList(listOf(CborPositiveInt(1u), CborString("test"))) + val list2 = CborList(listOf(CborPositiveInt(1u), CborString("test"))) + val list3 = CborList(listOf(CborPositiveInt(2u), CborString("test"))) + val list4 = CborList(listOf(CborPositiveInt(1u), CborString("test")), ulongArrayOf(1u)) + val list5 = CborList(listOf(CborPositiveInt(1u))) + + assertEquals(list1, list2) + assertEquals(list1.hashCode(), list2.hashCode()) + assertNotEquals(list1, list3) + assertNotEquals(list1, list4) + assertNotEquals(list1, list5) + assertNotEquals(list1, null as CborElement?) + assertNotEquals(list1 as CborElement, CborString("list")) + assertNotEquals(list1, CborString("list") as CborElement) + } + + @Test + fun testCborMapEquality() { + val map1 = CborMap(mapOf( + CborString("key1") to CborPositiveInt(1u), + CborString("key2") to CborString("value") + )) + val map2 = CborMap(mapOf( + CborString("key1") to CborPositiveInt(1u), + CborString("key2") to CborString("value") + )) + val map3 = CborMap(mapOf( + CborString("key1") to CborPositiveInt(2u), + CborString("key2") to CborString("value") + )) + val map4 = CborMap(mapOf( + CborString("key1") to CborPositiveInt(1u), + CborString("key2") to CborString("value") + ), ulongArrayOf(1u)) + val map5 = CborMap(mapOf( + CborString("key1") to CborPositiveInt(1u) + )) + + assertEquals(map1, map2) + assertEquals(map1.hashCode(), map2.hashCode()) + assertNotEquals(map1, map3) + assertNotEquals(map1, map4) + assertNotEquals(map1, map5) + assertNotEquals(map1, null as CborElement?) + assertNotEquals(map1 as CborElement, CborString("map")) + assertNotEquals(map1, CborString("map") as CborElement) + } + + @Test + fun testTagsEquality() { + val tags1 = ulongArrayOf(1u, 2u, 3u) + val tags2 = ulongArrayOf(1u, 2u, 3u) + val tags3 = ulongArrayOf(1u, 2u, 4u) + + val string1 = CborString("test", tags1) + val string2 = CborString("test", tags2) + val string3 = CborString("test", tags3) + + assertEquals(string1, string2) + assertEquals(string1.hashCode(), string2.hashCode()) + assertNotEquals(string1, string3) + } + + @Test + fun testEmptyCollectionsEquality() { + val emptyList1 = CborList(emptyList()) + val emptyList2 = CborList(emptyList()) + val emptyMap1 = CborMap(emptyMap()) + val emptyMap2 = CborMap(emptyMap()) + + assertEquals(emptyList1, emptyList2) + assertEquals(emptyList1.hashCode(), emptyList2.hashCode()) + assertEquals(emptyMap1, emptyMap2) + assertEquals(emptyMap1.hashCode(), emptyMap2.hashCode()) + assertNotEquals(emptyList1 as CborElement, emptyMap1) + assertNotEquals(emptyList1, emptyMap1 as CborElement) + } + + @Test + fun testNestedStructureEquality() { + val nested1 = CborMap(mapOf( + CborString("list") to CborList(listOf( + CborPositiveInt(1u), + CborMap(mapOf(CborString("inner") to CborNull())) + )) + )) + val nested2 = CborMap(mapOf( + CborString("list") to CborList(listOf( + CborPositiveInt(1u), + CborMap(mapOf(CborString("inner") to CborNull())) + )) + )) + val nested3 = CborMap(mapOf( + CborString("list") to CborList(listOf( + CborPositiveInt(2u), + CborMap(mapOf(CborString("inner") to CborNull())) + )) + )) + + assertEquals(nested1, nested2) + assertEquals(nested1.hashCode(), nested2.hashCode()) + assertNotEquals(nested1, nested3) + } + + @Test + fun testReflexiveEquality() { + val elements = listOf( + CborPositiveInt(42u), + CborNegativeInt(-42), + CborDouble(3.14), + CborString("test"), + CborBoolean(true), + CborByteString(byteArrayOf(1, 2, 3)), + CborNull(), + CborList(listOf(CborPositiveInt(1u))), + CborMap(mapOf(CborString("key") to CborPositiveInt(1u))) + ) + + elements.forEach { element -> + assertEquals(element, element, "Element should be equal to itself") + assertEquals(element.hashCode(), element.hashCode(), "Hash code should be consistent") + } + } + + @Test + fun testSymmetricEquality() { + val pairs = listOf( + CborPositiveInt(42u) to CborPositiveInt(42u), + CborNegativeInt(-42) to CborNegativeInt(-42), + CborDouble(3.14) to CborDouble(3.14), + CborString("test") to CborString("test"), + CborBoolean(true) to CborBoolean(true), + CborByteString(byteArrayOf(1, 2, 3)) to CborByteString(byteArrayOf(1, 2, 3)), + CborNull() to CborNull(), + CborList(listOf(CborPositiveInt(1u))) to CborList(listOf(CborPositiveInt(1u))), + CborMap(mapOf(CborString("key") to CborPositiveInt(1u))) to CborMap(mapOf(CborString("key") to CborPositiveInt(1u))) + ) + + pairs.forEach { (a, b) -> + assertEquals(a, b, "a should equal b") + assertEquals(b, a, "b should equal a (symmetry)") + assertEquals(a.hashCode(), b.hashCode(), "Hash codes should be equal") + } + } + + @Test + fun testTransitiveEquality() { + val a = CborString("test") + val b = CborString("test") + val c = CborString("test") + + assertEquals(a, b) + assertEquals(b, c) + assertEquals(a, c, "Transitivity: if a==b and b==c, then a==c") + } +} \ No newline at end of file diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborElementTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborElementTest.kt new file mode 100644 index 0000000000..9cd9e937f9 --- /dev/null +++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborElementTest.kt @@ -0,0 +1,339 @@ +/* + * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization.cbor + +import kotlinx.serialization.* +import kotlin.test.* + +class CborElementTest { + + private val cbor = Cbor {} + + /** + * Helper method to decode a hex string to a CborElement + */ + private fun decodeHexToCborElement(hexString: String): CborElement { + val bytes = HexConverter.parseHexBinary(hexString.uppercase()) + return cbor.decodeFromByteArray(bytes) + } + + @Test + fun testCborNull() { + val nullElement = CborNull() + val nullBytes = cbor.encodeToByteArray(nullElement) + val decodedNull = cbor.decodeFromByteArray(nullBytes) + assertEquals(nullElement, decodedNull) + } + + @Test + fun testCborNumber() { + val numberElement = CborPositiveInt(42u) + val numberBytes = cbor.encodeToByteArray(numberElement) + val decodedNumber = cbor.decodeFromByteArray(numberBytes) + assertEquals(numberElement, decodedNumber) + assertEquals(42u, (decodedNumber as CborPositiveInt).value) + } + + @Test + fun testCborString() { + val stringElement = CborString("Hello, CBOR!") + val stringBytes = cbor.encodeToByteArray(stringElement) + val decodedString = cbor.decodeFromByteArray(stringBytes) + assertEquals(stringElement, decodedString) + assertEquals("Hello, CBOR!", (decodedString as CborString).value) + } + + @Test + fun testCborBoolean() { + val booleanElement = CborBoolean(true) + val booleanBytes = cbor.encodeToByteArray(booleanElement) + val decodedBoolean = cbor.decodeFromByteArray(booleanBytes) + assertEquals(booleanElement, decodedBoolean) + assertEquals(true, (decodedBoolean as CborBoolean).value) + } + + @Test + fun testCborByteString() { + val byteArray = byteArrayOf(1, 2, 3, 4, 5) + val byteStringElement = CborByteString(byteArray) + val byteStringBytes = cbor.encodeToByteArray(byteStringElement) + val decodedByteString = cbor.decodeFromByteArray(byteStringBytes) + assertEquals(byteStringElement, decodedByteString) + assertTrue((decodedByteString as CborByteString).value.contentEquals(byteArray)) + } + + @Test + fun testCborList() { + val listElement = CborList( + listOf( + CborPositiveInt(1u), + CborString("two"), + CborBoolean(true), + CborNull() + ) + ) + val listBytes = cbor.encodeToByteArray(listElement) + val decodedList = cbor.decodeFromByteArray(listBytes) + + // Verify the type and size + assertTrue(decodedList is CborList) + assertEquals(4, decodedList.size) + + // Verify individual elements + assertTrue(decodedList[0] is CborPositiveInt) + assertEquals(1u, (decodedList[0] as CborPositiveInt).value) + + assertTrue(decodedList[1] is CborString) + assertEquals("two", (decodedList[1] as CborString).value) + + assertTrue(decodedList[2] is CborBoolean) + assertEquals(true, (decodedList[2] as CborBoolean).value) + + assertTrue(decodedList[3] is CborNull) + } + + @Test + fun testCborMap() { + val mapElement = CborMap( + mapOf( + CborString("key1") to CborPositiveInt(42u), + CborString("key2") to CborString("value"), + CborPositiveInt(3u) to CborBoolean(true), + CborNull() to CborNull() + ) + ) + val mapBytes = cbor.encodeToByteArray(mapElement) + val decodedMap = cbor.decodeFromByteArray(mapBytes) + + // Verify the type and size + assertTrue(decodedMap is CborMap) + assertEquals(4, decodedMap.size) + + // Verify individual entries + assertTrue(decodedMap.containsKey(CborString("key1"))) + val value1 = decodedMap[CborString("key1")] + assertTrue(value1 is CborPositiveInt) + assertEquals(42u, (value1 as CborPositiveInt).value) + + assertTrue(decodedMap.containsKey(CborString("key2"))) + val value2 = decodedMap[CborString("key2")] + assertTrue(value2 is CborString) + assertEquals("value", (value2 as CborString).value) + + assertTrue(decodedMap.containsKey(CborPositiveInt(3u))) + val value3 = decodedMap[CborPositiveInt(3u)] + assertTrue(value3 is CborBoolean) + assertEquals(true, (value3 as CborBoolean).value) + + assertTrue(decodedMap.containsKey(CborNull())) + val value4 = decodedMap[CborNull()] + assertTrue(value4 is CborNull) + } + + @Test + fun testComplexNestedStructure() { + // Create a complex nested structure with maps and lists + val complexElement = CborMap( + mapOf( + CborString("primitives") to CborList( + listOf( + CborPositiveInt(123u), + CborString("text"), + CborBoolean(false), + CborByteString(byteArrayOf(10, 20, 30)), + CborNull() + ) + ), + CborString("nested") to CborMap( + mapOf( + CborString("inner") to CborList( + listOf( + CborPositiveInt(1u), + CborPositiveInt(2u) + ) + ), + CborString("empty") to CborList(emptyList()) + ) + ) + ) + ) + + val complexBytes = cbor.encodeToByteArray(complexElement) + val decodedComplex = cbor.decodeFromByteArray(complexBytes) + + // Verify the type + assertTrue(decodedComplex is CborMap) + + // Verify the primitives list + assertTrue(decodedComplex.containsKey(CborString("primitives"))) + val primitivesValue = decodedComplex[CborString("primitives")] + assertTrue(primitivesValue is CborList) + + assertEquals(5, primitivesValue.size) + + assertTrue(primitivesValue[0] is CborPositiveInt) + assertEquals(123u, (primitivesValue[0] as CborPositiveInt).value) + + assertTrue(primitivesValue[1] is CborString) + assertEquals("text", (primitivesValue[1] as CborString).value) + + assertTrue(primitivesValue[2] is CborBoolean) + assertEquals(false, (primitivesValue[2] as CborBoolean).value) + + assertTrue(primitivesValue[3] is CborByteString) + assertTrue((primitivesValue[3] as CborByteString).value.contentEquals(byteArrayOf(10, 20, 30))) + + assertTrue(primitivesValue[4] is CborNull) + + // Verify the nested map + assertTrue(decodedComplex.containsKey(CborString("nested"))) + val nestedValue = decodedComplex[CborString("nested")] + assertTrue(nestedValue is CborMap) + + assertEquals(2, nestedValue.size) + + // Verify the inner list + assertTrue(nestedValue.containsKey(CborString("inner"))) + val innerValue = nestedValue[CborString("inner")] + assertTrue(innerValue is CborList) + + assertEquals(2, innerValue.size) + + assertTrue(innerValue[0] is CborPositiveInt) + assertEquals(1u, (innerValue[0] as CborPositiveInt).value) + + assertTrue(innerValue[1] is CborPositiveInt) + assertEquals(2u, (innerValue[1] as CborPositiveInt).value) + + // Verify the empty list + assertTrue(nestedValue.containsKey(CborString("empty"))) + val emptyValue = nestedValue[CborString("empty")] + assertTrue(emptyValue is CborList) + val empty = emptyValue + + assertEquals(0, empty.size) + } + + @Test + fun testDecodeIntegers() { + // Test data from CborParserTest.testParseIntegers + val element = decodeHexToCborElement("0C") as CborPositiveInt + assertEquals(12u, element.value) + + } + + @Test + fun testDecodeStrings() { + // Test data from CborParserTest.testParseStrings + val element = decodeHexToCborElement("6568656C6C6F") + assertTrue(element is CborString) + assertEquals("hello", element.value) + + val longStringElement = + decodeHexToCborElement("7828737472696E672074686174206973206C6F6E676572207468616E2032332063686172616374657273") + assertTrue(longStringElement is CborString) + assertEquals("string that is longer than 23 characters", longStringElement.value) + } + + @Test + fun testDecodeFloatingPoint() { + // Test data from CborParserTest.testParseDoubles + val doubleElement = decodeHexToCborElement("fb7e37e43c8800759c") + assertTrue(doubleElement is CborDouble) + assertEquals(1e+300, doubleElement.value) + + val floatElement = decodeHexToCborElement("fa47c35000") + assertTrue(floatElement is CborDouble) + assertEquals(100000.0f, floatElement.value.toFloat()) + } + + @Test + fun testDecodeByteString() { + // Test data from CborParserTest.testRfc7049IndefiniteByteStringExample + val element = decodeHexToCborElement("5F44aabbccdd43eeff99FF") + assertTrue(element is CborByteString) + val byteString = element as CborByteString + val expectedBytes = HexConverter.parseHexBinary("aabbccddeeff99") + assertTrue(byteString.value.contentEquals(expectedBytes)) + } + + @Test + fun testDecodeArray() { + // Test data from CborParserTest.testSkipCollections + val element = decodeHexToCborElement("830118ff1a00010000") + assertTrue(element is CborList) + val list = element as CborList + assertEquals(3, list.size) + assertEquals(1u, (list[0] as CborPositiveInt).value) + assertEquals(255u, (list[1] as CborPositiveInt).value) + assertEquals(65536u, (list[2] as CborPositiveInt).value) + } + + @Test + fun testDecodeMap() { + // Test data from CborParserTest.testSkipCollections + val element = decodeHexToCborElement("a26178676b6f746c696e7861796d73657269616c697a6174696f6e") + assertTrue(element is CborMap) + val map = element as CborMap + assertEquals(2, map.size) + assertEquals(CborString("kotlinx"), map[CborString("x")]) + assertEquals(CborString("serialization"), map[CborString("y")]) + } + + @Test + fun testDecodeComplexStructure() { + // Test data from CborParserTest.testSkipIndefiniteLength + val element = + decodeHexToCborElement("a461615f42cafe43010203ff61627f6648656c6c6f2065776f726c64ff61639f676b6f746c696e786d73657269616c697a6174696f6eff6164bf613101613202613303ff") + assertTrue(element is CborMap) + val map = element as CborMap + assertEquals(4, map.size) + + // Check the byte string + val byteString = map[CborString("a")] as CborByteString + val expectedBytes = HexConverter.parseHexBinary("cafe010203") + assertTrue(byteString.value.contentEquals(expectedBytes)) + + // Check the text string + assertEquals(CborString("Hello world"), map[CborString("b")]) + + // Check the array + val array = map[CborString("c")] as CborList + assertEquals(2, array.size) + assertEquals(CborString("kotlinx"), array[0]) + assertEquals(CborString("serialization"), array[1]) + + // Check the nested map + val nestedMap = map[CborString("d")] as CborMap + assertEquals(3, nestedMap.size) + assertEquals(CborPositiveInt(1u), nestedMap[CborString("1")]) + assertEquals(CborPositiveInt(2u), nestedMap[CborString("2")]) + assertEquals(CborPositiveInt(3u), nestedMap[CborString("3")]) + } + + // Test removed due to incompatibility with the new tag implementation + + @OptIn(ExperimentalStdlibApi::class) + @Test + fun testTagsRoundTrip() { + // Create a CborElement with tags + val originalElement = CborString("Hello, tagged world!", tags = ulongArrayOf(42u)) + + // Encode and decode + val bytes = cbor.encodeToByteArray(originalElement) + println(bytes.toHexString()) + val decodedElement = cbor.decodeFromByteArray(bytes) + + // Verify the value and tags + assertTrue(decodedElement is CborString) + assertEquals("Hello, tagged world!", decodedElement.value) + assertNotNull(decodedElement.tags) + assertEquals(1, decodedElement.tags.size) + assertEquals(42u, decodedElement.tags.first()) + } + + + +} diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborTaggedTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborTaggedTest.kt index fa884c0ab4..30a9daf512 100644 --- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborTaggedTest.kt +++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborTaggedTest.kt @@ -571,6 +571,9 @@ class CborTaggedTest { assertEquals(referenceHexString, cbor.encodeToHexString(ClassAsTagged.serializer(), reference)) assertEquals(reference, cbor.decodeFromHexString(ClassAsTagged.serializer(), referenceHexString)) + val struct = Cbor.CoseCompliant.encodeToCbor(reference) + assertEquals(Cbor.decodeFromByteArray(referenceHexString.hexToByteArray()), struct) + assertEquals( reference, Cbor { verifyObjectTags = false }.decodeFromHexString(ClassAsTagged.serializer(), referenceHexString) diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt index 330d4ff0f0..f37624a1ff 100644 --- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt +++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt @@ -5,6 +5,7 @@ package kotlinx.serialization.cbor import kotlinx.serialization.* +import kotlinx.serialization.cbor.CborSkipTagAndEmptyTest.DataClass import kotlin.test.* @@ -27,10 +28,14 @@ class CbrWriterTest { HexConverter.parseHexBinary("cafe"), HexConverter.parseHexBinary("cafe") ) + val encoded = + "bf637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973749f61616162ff636d6170bf01f502f4ff65696e6e6572bf6161636c6f6cff6a696e6e6572734c6973749fbf6161636b656bffff6a62797465537472696e6742cafe696279746541727261799f383521ffff" assertEquals( - "bf637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973749f61616162ff636d6170bf01f502f4ff65696e6e6572bf6161636c6f6cff6a696e6e6572734c6973749fbf6161636b656bffff6a62797465537472696e6742cafe696279746541727261799f383521ffff", + encoded, Cbor.encodeToHexString(TypesUmbrella.serializer(), test) ) + val struct = Cbor.encodeToCbor(test) + assertEquals(Cbor.decodeFromByteArray(encoded.hexToByteArray()), struct) } @Test @@ -46,10 +51,14 @@ class CbrWriterTest { HexConverter.parseHexBinary("cafe"), HexConverter.parseHexBinary("cafe") ) + val encoded = + "a9637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973748261616162636d6170a201f502f465696e6e6572a16161636c6f6c6a696e6e6572734c69737481a16161636b656b6a62797465537472696e6742cafe6962797465417272617982383521" assertEquals( - "a9637374726d48656c6c6f2c20776f726c64216169182a686e756c6c61626c65f6646c6973748261616162636d6170a201f502f465696e6e6572a16161636c6f6c6a696e6e6572734c69737481a16161636b656b6a62797465537472696e6742cafe6962797465417272617982383521", + encoded, Cbor { useDefiniteLengthEncoding = true }.encodeToHexString(TypesUmbrella.serializer(), test) ) + val struct = Cbor.encodeToCbor(test) + assertEquals(Cbor.decodeFromByteArray(encoded.hexToByteArray()), struct) } @Test @@ -62,10 +71,14 @@ class CbrWriterTest { true, 'a' ) + val encoded = + "bf63696e741a00018894646c6f6e671b7fffffffffffffff65666c6f6174fa4228000066646f75626c65fb4271fb0c5a2b700067626f6f6c65616ef564636861721861ff" assertEquals( - "bf63696e741a00018894646c6f6e671b7fffffffffffffff65666c6f6174fa4228000066646f75626c65fb4271fb0c5a2b700067626f6f6c65616ef564636861721861ff", + encoded, Cbor.encodeToHexString(NumberTypesUmbrella.serializer(), test) ) + val struct = Cbor.encodeToCbor(test) + assertEquals(Cbor.decodeFromByteArray(encoded.hexToByteArray()), struct) } @Test diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt index 8feb491191..cd5fea2891 100644 --- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt +++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt @@ -73,6 +73,8 @@ data class NullableByteString( @ByteString val byteString: ByteArray? ) { override fun equals(other: Any?): Boolean { + return toString() == other.toString() + /* if (this === other) return true if (other == null || this::class != other::class) return false @@ -83,7 +85,7 @@ data class NullableByteString( if (!byteString.contentEquals(other.byteString)) return false } else if (other.byteString != null) return false - return true + return true*/ } override fun hashCode(): Int { @@ -99,7 +101,7 @@ data class NullableByteStringDefaultNull( if (this === other) return true if (other == null || this::class != other::class) return false - other as NullableByteString + other as NullableByteStringDefaultNull if (byteString != null) { if (other.byteString == null) return false diff --git a/formats/json-io/build.gradle.kts b/formats/json-io/build.gradle.kts index 2effe4f2f5..334241fa09 100644 --- a/formats/json-io/build.gradle.kts +++ b/formats/json-io/build.gradle.kts @@ -33,12 +33,9 @@ kotlin { project.configureJava9ModuleInfo() -tasks.named("dokkaHtmlPartial") { - dokkaSourceSets { - configureEach { - externalDocumentationLink { - url.set(URI("https://kotlin.github.io/kotlinx-io/").toURL()) - } - } +dokka.dokkaSourceSets.configureEach { + externalDocumentationLinks.register("kotlinx-io") { + url("https://kotlinlang.org/api/kotlinx-io/") + packageListUrl = file("dokka/kotlinx-io.package-list").toURI() } } diff --git a/formats/json-io/dokka/kotlinx-io.package-list b/formats/json-io/dokka/kotlinx-io.package-list new file mode 100644 index 0000000000..867ab49bd0 --- /dev/null +++ b/formats/json-io/dokka/kotlinx-io.package-list @@ -0,0 +1,10 @@ +$dokka.format:html-v1 +$dokka.linkExtension:html + +module:kotlinx-io-bytestring +kotlinx.io.bytestring +module:kotlinx-io-core +kotlinx.io +kotlinx.io.files +module:kotlinx-io-okio +kotlinx.io.okio diff --git a/formats/json-okio/build.gradle.kts b/formats/json-okio/build.gradle.kts index 93513985c1..1d165755a9 100644 --- a/formats/json-okio/build.gradle.kts +++ b/formats/json-okio/build.gradle.kts @@ -34,15 +34,9 @@ kotlin { project.configureJava9ModuleInfo() -tasks.named("dokkaHtmlPartial") { - dokkaSourceSets { - configureEach { - externalDocumentationLink { - url.set(URI("https://square.github.io/okio/3.x/okio/").toURL()) - packageListUrl.set( - file("dokka/okio.package.list").toURI().toURL() - ) - } - } +dokka.dokkaSourceSets.configureEach { + externalDocumentationLinks.register("okio") { + url("https://square.github.io/okio/3.x/okio") + packageListUrl = file("dokka/okio.package-list").toURI() } } diff --git a/formats/json-okio/dokka/okio.package.list b/formats/json-okio/dokka/okio.package-list similarity index 89% rename from formats/json-okio/dokka/okio.package.list rename to formats/json-okio/dokka/okio.package-list index 96713f52e9..2673ba6038 100644 --- a/formats/json-okio/dokka/okio.package.list +++ b/formats/json-okio/dokka/okio.package-list @@ -2,23 +2,27 @@ $dokka.format:html-v1 $dokka.linkExtension:html $dokka.location:okio////PointingToDeclaration/okio/okio/index.html $dokka.location:okio//Okio/#/PointingToDeclaration/okio/okio/-okio.html +$dokka.location:okio//SYSTEM/okio.FileSystem.Companion#/PointingToDeclaration/okio/okio/-s-y-s-t-e-m.html $dokka.location:okio//Utf8/#/PointingToDeclaration/okio/okio/-utf8.html $dokka.location:okio//appendingSink/java.io.File#/PointingToDeclaration/okio/okio/appending-sink.html +$dokka.location:okio//asOkioSocket/java.net.Socket#/PointingToDeclaration/okio/okio/as-okio-socket.html $dokka.location:okio//asResourceFileSystem/java.lang.ClassLoader#/PointingToDeclaration/okio/okio/as-resource-file-system.html $dokka.location:okio//blackholeSink/#/PointingToDeclaration/okio/okio/blackhole-sink.html $dokka.location:okio//buffer/okio.Sink#/PointingToDeclaration/okio/okio/buffer.html $dokka.location:okio//buffer/okio.Source#/PointingToDeclaration/okio/okio/buffer.html $dokka.location:okio//cipherSink/okio.Sink#javax.crypto.Cipher/PointingToDeclaration/okio/okio/cipher-sink.html $dokka.location:okio//cipherSource/okio.Source#javax.crypto.Cipher/PointingToDeclaration/okio/okio/cipher-source.html -$dokka.location:okio//deflate/okio.Sink#java.util.zip.Deflater/PointingToDeclaration/okio/okio/deflate.html +$dokka.location:okio//deflate/okio.Sink#okio.Deflater/PointingToDeclaration/okio/okio/deflate.html $dokka.location:okio//gzip/okio.Sink#/PointingToDeclaration/okio/okio/gzip.html $dokka.location:okio//gzip/okio.Source#/PointingToDeclaration/okio/okio/gzip.html $dokka.location:okio//hashingSink/okio.Sink#java.security.MessageDigest/PointingToDeclaration/okio/okio/hashing-sink.html $dokka.location:okio//hashingSink/okio.Sink#javax.crypto.Mac/PointingToDeclaration/okio/okio/hashing-sink.html $dokka.location:okio//hashingSource/okio.Source#java.security.MessageDigest/PointingToDeclaration/okio/okio/hashing-source.html $dokka.location:okio//hashingSource/okio.Source#javax.crypto.Mac/PointingToDeclaration/okio/okio/hashing-source.html -$dokka.location:okio//inflate/okio.Source#java.util.zip.Inflater/PointingToDeclaration/okio/okio/inflate.html +$dokka.location:okio//inMemorySocketPair/#kotlin.Long/PointingToDeclaration/okio/okio/in-memory-socket-pair.html +$dokka.location:okio//inflate/okio.Source#okio.Inflater/PointingToDeclaration/okio/okio/inflate.html $dokka.location:okio//openZip/okio.FileSystem#okio.Path/PointingToDeclaration/okio/okio/open-zip.html +$dokka.location:okio//readByteString/kotlinx.cinterop.CPointer[kotlinx.cinterop.CPointed]#kotlin.Int/PointingToDeclaration/okio/okio/read-byte-string.html $dokka.location:okio//sink/java.io.File#kotlin.Boolean/PointingToDeclaration/okio/okio/sink.html $dokka.location:okio//sink/java.io.OutputStream#/PointingToDeclaration/okio/okio/sink.html $dokka.location:okio//sink/java.net.Socket#/PointingToDeclaration/okio/okio/sink.html @@ -29,14 +33,13 @@ $dokka.location:okio//source/java.net.Socket#/PointingToDeclaration/okio/okio/s $dokka.location:okio//source/java.nio.file.Path#kotlin.Array[java.nio.file.OpenOption]/PointingToDeclaration/okio/okio/source.html $dokka.location:okio//use/TypeParam(bounds=[okio.Closeable?])#kotlin.Function1[TypeParam(bounds=[okio.Closeable?]),TypeParam(bounds=[kotlin.Any?])]/PointingToDeclaration/okio/okio/use.html $dokka.location:okio//utf8Size/kotlin.String#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/utf8-size.html +$dokka.location:okio//withLock/java.util.concurrent.locks.ReentrantLock#kotlin.Function0[TypeParam(bounds=[kotlin.Any?])]/PointingToDeclaration/okio/okio/with-lock.html $dokka.location:okio//withLock/okio.Lock#kotlin.Function0[TypeParam(bounds=[kotlin.Any?])]/PointingToDeclaration/okio/okio/with-lock.html $dokka.location:okio/ArrayIndexOutOfBoundsException///PointingToDeclaration/okio/okio/-array-index-out-of-bounds-exception/index.html $dokka.location:okio/ArrayIndexOutOfBoundsException/ArrayIndexOutOfBoundsException/#kotlin.String?/PointingToDeclaration/okio/okio/-array-index-out-of-bounds-exception/-array-index-out-of-bounds-exception.html -$dokka.location:okio/AsyncTimeout.Companion///PointingToDeclaration/okio/okio/-async-timeout/-companion/index.html -$dokka.location:okio/AsyncTimeout.Companion/condition/#/PointingToDeclaration/okio/okio/-async-timeout/-companion/condition.html -$dokka.location:okio/AsyncTimeout.Companion/lock/#/PointingToDeclaration/okio/okio/-async-timeout/-companion/lock.html $dokka.location:okio/AsyncTimeout///PointingToDeclaration/okio/okio/-async-timeout/index.html $dokka.location:okio/AsyncTimeout/AsyncTimeout/#/PointingToDeclaration/okio/okio/-async-timeout/-async-timeout.html +$dokka.location:okio/AsyncTimeout/cancel/#/PointingToDeclaration/okio/okio/-async-timeout/cancel.html $dokka.location:okio/AsyncTimeout/enter/#/PointingToDeclaration/okio/okio/-async-timeout/enter.html $dokka.location:okio/AsyncTimeout/exit/#/PointingToDeclaration/okio/okio/-async-timeout/exit.html $dokka.location:okio/AsyncTimeout/sink/#okio.Sink/PointingToDeclaration/okio/okio/-async-timeout/sink.html @@ -60,7 +63,7 @@ $dokka.location:okio/Buffer/Buffer/#/PointingToDeclaration/okio/okio/-buffer/-b $dokka.location:okio/Buffer/buffer/#/PointingToDeclaration/okio/okio/-buffer/buffer.html $dokka.location:okio/Buffer/clear/#/PointingToDeclaration/okio/okio/-buffer/clear.html $dokka.location:okio/Buffer/clone/#/PointingToDeclaration/okio/okio/-buffer/clone.html -$dokka.location:okio/Buffer/close/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]close.html +$dokka.location:okio/Buffer/close/#/PointingToDeclaration/okio/okio/-buffer/close.html $dokka.location:okio/Buffer/completeSegmentByteCount/#/PointingToDeclaration/okio/okio/-buffer/complete-segment-byte-count.html $dokka.location:okio/Buffer/copy/#/PointingToDeclaration/okio/okio/-buffer/copy.html $dokka.location:okio/Buffer/copyTo/#java.io.OutputStream#kotlin.Long#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/copy-to.html @@ -69,62 +72,64 @@ $dokka.location:okio/Buffer/copyTo/#okio.Buffer#kotlin.Long/PointingToDeclaratio $dokka.location:okio/Buffer/emit/#/PointingToDeclaration/okio/okio/-buffer/emit.html $dokka.location:okio/Buffer/emitCompleteSegments/#/PointingToDeclaration/okio/okio/-buffer/emit-complete-segments.html $dokka.location:okio/Buffer/equals/#kotlin.Any?/PointingToDeclaration/okio/okio/-buffer/[non-jvm]equals.html -$dokka.location:okio/Buffer/exhausted/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]exhausted.html -$dokka.location:okio/Buffer/flush/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]flush.html +$dokka.location:okio/Buffer/exhausted/#/PointingToDeclaration/okio/okio/-buffer/exhausted.html +$dokka.location:okio/Buffer/flush/#/PointingToDeclaration/okio/okio/-buffer/flush.html $dokka.location:okio/Buffer/get/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/get.html $dokka.location:okio/Buffer/hashCode/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]hash-code.html $dokka.location:okio/Buffer/hmacSha1/#okio.ByteString/PointingToDeclaration/okio/okio/-buffer/hmac-sha1.html $dokka.location:okio/Buffer/hmacSha256/#okio.ByteString/PointingToDeclaration/okio/okio/-buffer/hmac-sha256.html $dokka.location:okio/Buffer/hmacSha512/#okio.ByteString/PointingToDeclaration/okio/okio/-buffer/hmac-sha512.html -$dokka.location:okio/Buffer/indexOf/#kotlin.Byte#kotlin.Long#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]index-of.html -$dokka.location:okio/Buffer/indexOf/#kotlin.Byte#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]index-of.html -$dokka.location:okio/Buffer/indexOf/#kotlin.Byte/PointingToDeclaration/okio/okio/-buffer/[non-jvm]index-of.html -$dokka.location:okio/Buffer/indexOf/#okio.ByteString#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]index-of.html -$dokka.location:okio/Buffer/indexOf/#okio.ByteString/PointingToDeclaration/okio/okio/-buffer/[non-jvm]index-of.html -$dokka.location:okio/Buffer/indexOfElement/#okio.ByteString#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]index-of-element.html -$dokka.location:okio/Buffer/indexOfElement/#okio.ByteString/PointingToDeclaration/okio/okio/-buffer/[non-jvm]index-of-element.html +$dokka.location:okio/Buffer/indexOf/#kotlin.Byte#kotlin.Long#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/index-of.html +$dokka.location:okio/Buffer/indexOf/#kotlin.Byte#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/index-of.html +$dokka.location:okio/Buffer/indexOf/#kotlin.Byte/PointingToDeclaration/okio/okio/-buffer/index-of.html +$dokka.location:okio/Buffer/indexOf/#okio.ByteString#kotlin.Long#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/index-of.html +$dokka.location:okio/Buffer/indexOf/#okio.ByteString#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/index-of.html +$dokka.location:okio/Buffer/indexOf/#okio.ByteString/PointingToDeclaration/okio/okio/-buffer/index-of.html +$dokka.location:okio/Buffer/indexOfElement/#okio.ByteString#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/index-of-element.html +$dokka.location:okio/Buffer/indexOfElement/#okio.ByteString/PointingToDeclaration/okio/okio/-buffer/index-of-element.html $dokka.location:okio/Buffer/inputStream/#/PointingToDeclaration/okio/okio/-buffer/input-stream.html $dokka.location:okio/Buffer/isOpen/#/PointingToDeclaration/okio/okio/-buffer/is-open.html $dokka.location:okio/Buffer/md5/#/PointingToDeclaration/okio/okio/-buffer/md5.html $dokka.location:okio/Buffer/outputStream/#/PointingToDeclaration/okio/okio/-buffer/output-stream.html -$dokka.location:okio/Buffer/peek/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]peek.html -$dokka.location:okio/Buffer/rangeEquals/#kotlin.Long#okio.ByteString#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-buffer/[non-jvm]range-equals.html -$dokka.location:okio/Buffer/rangeEquals/#kotlin.Long#okio.ByteString/PointingToDeclaration/okio/okio/-buffer/[non-jvm]range-equals.html +$dokka.location:okio/Buffer/peek/#/PointingToDeclaration/okio/okio/-buffer/peek.html +$dokka.location:okio/Buffer/rangeEquals/#kotlin.Long#okio.ByteString#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-buffer/range-equals.html +$dokka.location:okio/Buffer/rangeEquals/#kotlin.Long#okio.ByteString/PointingToDeclaration/okio/okio/-buffer/range-equals.html $dokka.location:okio/Buffer/read/#java.nio.ByteBuffer/PointingToDeclaration/okio/okio/-buffer/read.html -$dokka.location:okio/Buffer/read/#kotlin.ByteArray#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read.html -$dokka.location:okio/Buffer/read/#kotlin.ByteArray/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read.html -$dokka.location:okio/Buffer/read/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read.html -$dokka.location:okio/Buffer/readAll/#okio.Sink/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-all.html +$dokka.location:okio/Buffer/read/#kotlin.ByteArray#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-buffer/read.html +$dokka.location:okio/Buffer/read/#kotlin.ByteArray/PointingToDeclaration/okio/okio/-buffer/read.html +$dokka.location:okio/Buffer/read/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/read.html +$dokka.location:okio/Buffer/readAll/#okio.Sink/PointingToDeclaration/okio/okio/-buffer/read-all.html $dokka.location:okio/Buffer/readAndWriteUnsafe/#okio.Buffer.UnsafeCursor/PointingToDeclaration/okio/okio/-buffer/read-and-write-unsafe.html -$dokka.location:okio/Buffer/readByte/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-byte.html -$dokka.location:okio/Buffer/readByteArray/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-byte-array.html -$dokka.location:okio/Buffer/readByteArray/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-byte-array.html -$dokka.location:okio/Buffer/readByteString/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-byte-string.html -$dokka.location:okio/Buffer/readByteString/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-byte-string.html -$dokka.location:okio/Buffer/readDecimalLong/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-decimal-long.html +$dokka.location:okio/Buffer/readByte/#/PointingToDeclaration/okio/okio/-buffer/read-byte.html +$dokka.location:okio/Buffer/readByteArray/#/PointingToDeclaration/okio/okio/-buffer/read-byte-array.html +$dokka.location:okio/Buffer/readByteArray/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/read-byte-array.html +$dokka.location:okio/Buffer/readByteString/#/PointingToDeclaration/okio/okio/-buffer/read-byte-string.html +$dokka.location:okio/Buffer/readByteString/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/read-byte-string.html +$dokka.location:okio/Buffer/readDecimalLong/#/PointingToDeclaration/okio/okio/-buffer/read-decimal-long.html $dokka.location:okio/Buffer/readFrom/#java.io.InputStream#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/read-from.html $dokka.location:okio/Buffer/readFrom/#java.io.InputStream/PointingToDeclaration/okio/okio/-buffer/read-from.html -$dokka.location:okio/Buffer/readFully/#kotlin.ByteArray/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-fully.html -$dokka.location:okio/Buffer/readFully/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-fully.html -$dokka.location:okio/Buffer/readHexadecimalUnsignedLong/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-hexadecimal-unsigned-long.html -$dokka.location:okio/Buffer/readInt/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-int.html -$dokka.location:okio/Buffer/readIntLe/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-int-le.html -$dokka.location:okio/Buffer/readLong/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-long.html -$dokka.location:okio/Buffer/readLongLe/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-long-le.html -$dokka.location:okio/Buffer/readShort/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-short.html -$dokka.location:okio/Buffer/readShortLe/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-short-le.html +$dokka.location:okio/Buffer/readFully/#kotlin.ByteArray/PointingToDeclaration/okio/okio/-buffer/read-fully.html +$dokka.location:okio/Buffer/readFully/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/read-fully.html +$dokka.location:okio/Buffer/readHexadecimalUnsignedLong/#/PointingToDeclaration/okio/okio/-buffer/read-hexadecimal-unsigned-long.html +$dokka.location:okio/Buffer/readInt/#/PointingToDeclaration/okio/okio/-buffer/read-int.html +$dokka.location:okio/Buffer/readIntLe/#/PointingToDeclaration/okio/okio/-buffer/read-int-le.html +$dokka.location:okio/Buffer/readLong/#/PointingToDeclaration/okio/okio/-buffer/read-long.html +$dokka.location:okio/Buffer/readLongLe/#/PointingToDeclaration/okio/okio/-buffer/read-long-le.html +$dokka.location:okio/Buffer/readShort/#/PointingToDeclaration/okio/okio/-buffer/read-short.html +$dokka.location:okio/Buffer/readShortLe/#/PointingToDeclaration/okio/okio/-buffer/read-short-le.html $dokka.location:okio/Buffer/readString/#java.nio.charset.Charset/PointingToDeclaration/okio/okio/-buffer/read-string.html $dokka.location:okio/Buffer/readString/#kotlin.Long#java.nio.charset.Charset/PointingToDeclaration/okio/okio/-buffer/read-string.html $dokka.location:okio/Buffer/readUnsafe/#okio.Buffer.UnsafeCursor/PointingToDeclaration/okio/okio/-buffer/read-unsafe.html -$dokka.location:okio/Buffer/readUtf8/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-utf8.html -$dokka.location:okio/Buffer/readUtf8/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-utf8.html -$dokka.location:okio/Buffer/readUtf8CodePoint/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-utf8-code-point.html -$dokka.location:okio/Buffer/readUtf8Line/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-utf8-line.html -$dokka.location:okio/Buffer/readUtf8LineStrict/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-utf8-line-strict.html -$dokka.location:okio/Buffer/readUtf8LineStrict/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-utf8-line-strict.html -$dokka.location:okio/Buffer/request/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]request.html -$dokka.location:okio/Buffer/require/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]require.html -$dokka.location:okio/Buffer/select/#okio.Options/PointingToDeclaration/okio/okio/-buffer/[non-jvm]select.html +$dokka.location:okio/Buffer/readUtf8/#/PointingToDeclaration/okio/okio/-buffer/read-utf8.html +$dokka.location:okio/Buffer/readUtf8/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/read-utf8.html +$dokka.location:okio/Buffer/readUtf8CodePoint/#/PointingToDeclaration/okio/okio/-buffer/read-utf8-code-point.html +$dokka.location:okio/Buffer/readUtf8Line/#/PointingToDeclaration/okio/okio/-buffer/read-utf8-line.html +$dokka.location:okio/Buffer/readUtf8LineStrict/#/PointingToDeclaration/okio/okio/-buffer/read-utf8-line-strict.html +$dokka.location:okio/Buffer/readUtf8LineStrict/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/read-utf8-line-strict.html +$dokka.location:okio/Buffer/request/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/request.html +$dokka.location:okio/Buffer/require/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/require.html +$dokka.location:okio/Buffer/select/#okio.Options/PointingToDeclaration/okio/okio/-buffer/select.html +$dokka.location:okio/Buffer/select/#okio.TypedOptions[TypeParam(bounds=[kotlin.Any])]/PointingToDeclaration/okio/okio/-buffer/select.html $dokka.location:okio/Buffer/sha1/#/PointingToDeclaration/okio/okio/-buffer/sha1.html $dokka.location:okio/Buffer/sha256/#/PointingToDeclaration/okio/okio/-buffer/sha256.html $dokka.location:okio/Buffer/sha512/#/PointingToDeclaration/okio/okio/-buffer/sha512.html @@ -132,16 +137,16 @@ $dokka.location:okio/Buffer/size/#/PointingToDeclaration/okio/okio/-buffer/size $dokka.location:okio/Buffer/skip/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/skip.html $dokka.location:okio/Buffer/snapshot/#/PointingToDeclaration/okio/okio/-buffer/snapshot.html $dokka.location:okio/Buffer/snapshot/#kotlin.Int/PointingToDeclaration/okio/okio/-buffer/snapshot.html -$dokka.location:okio/Buffer/timeout/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]timeout.html +$dokka.location:okio/Buffer/timeout/#/PointingToDeclaration/okio/okio/-buffer/timeout.html $dokka.location:okio/Buffer/toString/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]to-string.html $dokka.location:okio/Buffer/write/#java.nio.ByteBuffer/PointingToDeclaration/okio/okio/-buffer/write.html $dokka.location:okio/Buffer/write/#kotlin.ByteArray#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-buffer/write.html $dokka.location:okio/Buffer/write/#kotlin.ByteArray/PointingToDeclaration/okio/okio/-buffer/write.html -$dokka.location:okio/Buffer/write/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]write.html +$dokka.location:okio/Buffer/write/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/write.html $dokka.location:okio/Buffer/write/#okio.ByteString#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-buffer/write.html $dokka.location:okio/Buffer/write/#okio.ByteString/PointingToDeclaration/okio/okio/-buffer/write.html $dokka.location:okio/Buffer/write/#okio.Source#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/write.html -$dokka.location:okio/Buffer/writeAll/#okio.Source/PointingToDeclaration/okio/okio/-buffer/[non-jvm]write-all.html +$dokka.location:okio/Buffer/writeAll/#okio.Source/PointingToDeclaration/okio/okio/-buffer/write-all.html $dokka.location:okio/Buffer/writeByte/#kotlin.Int/PointingToDeclaration/okio/okio/-buffer/write-byte.html $dokka.location:okio/Buffer/writeDecimalLong/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/write-decimal-long.html $dokka.location:okio/Buffer/writeHexadecimalUnsignedLong/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/write-hexadecimal-unsigned-long.html @@ -189,6 +194,7 @@ $dokka.location:okio/BufferedSource/exhausted/#/PointingToDeclaration/okio/okio $dokka.location:okio/BufferedSource/indexOf/#kotlin.Byte#kotlin.Long#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-source/index-of.html $dokka.location:okio/BufferedSource/indexOf/#kotlin.Byte#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-source/index-of.html $dokka.location:okio/BufferedSource/indexOf/#kotlin.Byte/PointingToDeclaration/okio/okio/-buffered-source/index-of.html +$dokka.location:okio/BufferedSource/indexOf/#okio.ByteString#kotlin.Long#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-source/index-of.html $dokka.location:okio/BufferedSource/indexOf/#okio.ByteString#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-source/index-of.html $dokka.location:okio/BufferedSource/indexOf/#okio.ByteString/PointingToDeclaration/okio/okio/-buffered-source/index-of.html $dokka.location:okio/BufferedSource/indexOfElement/#okio.ByteString#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-source/index-of-element.html @@ -226,6 +232,7 @@ $dokka.location:okio/BufferedSource/readUtf8LineStrict/#kotlin.Long/PointingToDe $dokka.location:okio/BufferedSource/request/#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-source/request.html $dokka.location:okio/BufferedSource/require/#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-source/require.html $dokka.location:okio/BufferedSource/select/#okio.Options/PointingToDeclaration/okio/okio/-buffered-source/select.html +$dokka.location:okio/BufferedSource/select/#okio.TypedOptions[TypeParam(bounds=[kotlin.Any])]/PointingToDeclaration/okio/okio/-buffered-source/select.html $dokka.location:okio/BufferedSource/skip/#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-source/skip.html $dokka.location:okio/ByteString.Companion///PointingToDeclaration/okio/okio/-byte-string/-companion/index.html $dokka.location:okio/ByteString.Companion/EMPTY/#/PointingToDeclaration/okio/okio/-byte-string/-companion/-e-m-p-t-y.html @@ -235,9 +242,9 @@ $dokka.location:okio/ByteString.Companion/encode/kotlin.String#java.nio.charset. $dokka.location:okio/ByteString.Companion/encodeUtf8/kotlin.String#/PointingToDeclaration/okio/okio/-byte-string/-companion/encode-utf8.html $dokka.location:okio/ByteString.Companion/of/#kotlin.ByteArray/PointingToDeclaration/okio/okio/-byte-string/-companion/of.html $dokka.location:okio/ByteString.Companion/readByteString/java.io.InputStream#kotlin.Int/PointingToDeclaration/okio/okio/-byte-string/-companion/read-byte-string.html -$dokka.location:okio/ByteString.Companion/toByteString/[Error type: Unresolved type for NSData]#/PointingToDeclaration/okio/okio/-byte-string/-companion/to-byte-string.html $dokka.location:okio/ByteString.Companion/toByteString/java.nio.ByteBuffer#/PointingToDeclaration/okio/okio/-byte-string/-companion/to-byte-string.html $dokka.location:okio/ByteString.Companion/toByteString/kotlin.ByteArray#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-byte-string/-companion/to-byte-string.html +$dokka.location:okio/ByteString.Companion/toByteString/platform.Foundation.NSData#/PointingToDeclaration/okio/okio/-byte-string/-companion/to-byte-string.html $dokka.location:okio/ByteString///PointingToDeclaration/okio/okio/-byte-string/index.html $dokka.location:okio/ByteString/asByteBuffer/#/PointingToDeclaration/okio/okio/-byte-string/as-byte-buffer.html $dokka.location:okio/ByteString/base64/#/PointingToDeclaration/okio/okio/-byte-string/base64.html @@ -289,14 +296,22 @@ $dokka.location:okio/CipherSource/read/#okio.Buffer#kotlin.Long/PointingToDeclar $dokka.location:okio/CipherSource/timeout/#/PointingToDeclaration/okio/okio/-cipher-source/timeout.html $dokka.location:okio/Closeable///PointingToDeclaration/okio/okio/-closeable/index.html $dokka.location:okio/Closeable/close/#/PointingToDeclaration/okio/okio/-closeable/close.html +$dokka.location:okio/Deflater///PointingToDeclaration/okio/okio/-deflater/index.html +$dokka.location:okio/Deflater/Deflater/#/PointingToDeclaration/okio/okio/-deflater/-deflater.html +$dokka.location:okio/Deflater/Deflater/#kotlin.Int#kotlin.Boolean/PointingToDeclaration/okio/okio/-deflater/-deflater.html +$dokka.location:okio/Deflater/end/#/PointingToDeclaration/okio/okio/-deflater/end.html +$dokka.location:okio/Deflater/flush/#/PointingToDeclaration/okio/okio/-deflater/flush.html +$dokka.location:okio/Deflater/getBytesRead/#/PointingToDeclaration/okio/okio/-deflater/get-bytes-read.html $dokka.location:okio/DeflaterSink///PointingToDeclaration/okio/okio/-deflater-sink/index.html $dokka.location:okio/DeflaterSink/DeflaterSink/#okio.Sink#java.util.zip.Deflater/PointingToDeclaration/okio/okio/-deflater-sink/-deflater-sink.html +$dokka.location:okio/DeflaterSink/DeflaterSink/#okio.Sink#okio.Deflater/PointingToDeclaration/okio/okio/-deflater-sink/-deflater-sink.html $dokka.location:okio/DeflaterSink/close/#/PointingToDeclaration/okio/okio/-deflater-sink/close.html $dokka.location:okio/DeflaterSink/flush/#/PointingToDeclaration/okio/okio/-deflater-sink/flush.html $dokka.location:okio/DeflaterSink/timeout/#/PointingToDeclaration/okio/okio/-deflater-sink/timeout.html $dokka.location:okio/DeflaterSink/toString/#/PointingToDeclaration/okio/okio/-deflater-sink/to-string.html $dokka.location:okio/DeflaterSink/write/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-deflater-sink/write.html $dokka.location:okio/EOFException///PointingToDeclaration/okio/okio/-e-o-f-exception/index.html +$dokka.location:okio/EOFException/EOFException/#/PointingToDeclaration/okio/okio/-e-o-f-exception/-e-o-f-exception.html $dokka.location:okio/EOFException/EOFException/#kotlin.String?/PointingToDeclaration/okio/okio/-e-o-f-exception/-e-o-f-exception.html $dokka.location:okio/FileHandle///PointingToDeclaration/okio/okio/-file-handle/index.html $dokka.location:okio/FileHandle/FileHandle/#kotlin.Boolean/PointingToDeclaration/okio/okio/-file-handle/-file-handle.html @@ -343,6 +358,7 @@ $dokka.location:okio/FileSystem/appendingSink/#okio.Path#kotlin.Boolean/Pointing $dokka.location:okio/FileSystem/appendingSink/#okio.Path/PointingToDeclaration/okio/okio/-file-system/appending-sink.html $dokka.location:okio/FileSystem/atomicMove/#okio.Path#okio.Path/PointingToDeclaration/okio/okio/-file-system/atomic-move.html $dokka.location:okio/FileSystem/canonicalize/#okio.Path/PointingToDeclaration/okio/okio/-file-system/canonicalize.html +$dokka.location:okio/FileSystem/close/#/PointingToDeclaration/okio/okio/-file-system/close.html $dokka.location:okio/FileSystem/copy/#okio.Path#okio.Path/PointingToDeclaration/okio/okio/-file-system/copy.html $dokka.location:okio/FileSystem/createDirectories/#okio.Path#kotlin.Boolean/PointingToDeclaration/okio/okio/-file-system/create-directories.html $dokka.location:okio/FileSystem/createDirectories/#okio.Path/PointingToDeclaration/okio/okio/-file-system/create-directories.html @@ -373,6 +389,7 @@ $dokka.location:okio/ForwardingFileSystem/ForwardingFileSystem/#okio.FileSystem/ $dokka.location:okio/ForwardingFileSystem/appendingSink/#okio.Path#kotlin.Boolean/PointingToDeclaration/okio/okio/-forwarding-file-system/appending-sink.html $dokka.location:okio/ForwardingFileSystem/atomicMove/#okio.Path#okio.Path/PointingToDeclaration/okio/okio/-forwarding-file-system/atomic-move.html $dokka.location:okio/ForwardingFileSystem/canonicalize/#okio.Path/PointingToDeclaration/okio/okio/-forwarding-file-system/canonicalize.html +$dokka.location:okio/ForwardingFileSystem/close/#/PointingToDeclaration/okio/okio/-forwarding-file-system/close.html $dokka.location:okio/ForwardingFileSystem/createDirectory/#okio.Path#kotlin.Boolean/PointingToDeclaration/okio/okio/-forwarding-file-system/create-directory.html $dokka.location:okio/ForwardingFileSystem/createSymlink/#okio.Path#okio.Path/PointingToDeclaration/okio/okio/-forwarding-file-system/create-symlink.html $dokka.location:okio/ForwardingFileSystem/delegate/#/PointingToDeclaration/okio/okio/-forwarding-file-system/delegate.html @@ -405,6 +422,8 @@ $dokka.location:okio/ForwardingSource/timeout/#/PointingToDeclaration/okio/okio $dokka.location:okio/ForwardingSource/toString/#/PointingToDeclaration/okio/okio/-forwarding-source/to-string.html $dokka.location:okio/ForwardingTimeout///PointingToDeclaration/okio/okio/-forwarding-timeout/index.html $dokka.location:okio/ForwardingTimeout/ForwardingTimeout/#okio.Timeout/PointingToDeclaration/okio/okio/-forwarding-timeout/-forwarding-timeout.html +$dokka.location:okio/ForwardingTimeout/awaitSignal/#java.util.concurrent.locks.Condition/PointingToDeclaration/okio/okio/-forwarding-timeout/await-signal.html +$dokka.location:okio/ForwardingTimeout/cancel/#/PointingToDeclaration/okio/okio/-forwarding-timeout/cancel.html $dokka.location:okio/ForwardingTimeout/clearDeadline/#/PointingToDeclaration/okio/okio/-forwarding-timeout/clear-deadline.html $dokka.location:okio/ForwardingTimeout/clearTimeout/#/PointingToDeclaration/okio/okio/-forwarding-timeout/clear-timeout.html $dokka.location:okio/ForwardingTimeout/deadlineNanoTime/#/PointingToDeclaration/okio/okio/-forwarding-timeout/deadline-nano-time.html @@ -415,6 +434,7 @@ $dokka.location:okio/ForwardingTimeout/setDelegate/#okio.Timeout/PointingToDecla $dokka.location:okio/ForwardingTimeout/throwIfReached/#/PointingToDeclaration/okio/okio/-forwarding-timeout/throw-if-reached.html $dokka.location:okio/ForwardingTimeout/timeout/#kotlin.Long#java.util.concurrent.TimeUnit/PointingToDeclaration/okio/okio/-forwarding-timeout/timeout.html $dokka.location:okio/ForwardingTimeout/timeoutNanos/#/PointingToDeclaration/okio/okio/-forwarding-timeout/timeout-nanos.html +$dokka.location:okio/ForwardingTimeout/waitUntilNotified/#kotlin.Any/PointingToDeclaration/okio/okio/-forwarding-timeout/wait-until-notified.html $dokka.location:okio/GzipSink///PointingToDeclaration/okio/okio/-gzip-sink/index.html $dokka.location:okio/GzipSink/GzipSink/#okio.Sink/PointingToDeclaration/okio/okio/-gzip-sink/-gzip-sink.html $dokka.location:okio/GzipSink/close/#/PointingToDeclaration/okio/okio/-gzip-sink/close.html @@ -440,7 +460,7 @@ $dokka.location:okio/HashingSink/close/#/PointingToDeclaration/okio/okio/-hashi $dokka.location:okio/HashingSink/flush/#/PointingToDeclaration/okio/okio/-hashing-sink/flush.html $dokka.location:okio/HashingSink/hash/#/PointingToDeclaration/okio/okio/-hashing-sink/hash.html $dokka.location:okio/HashingSink/timeout/#/PointingToDeclaration/okio/okio/-hashing-sink/timeout.html -$dokka.location:okio/HashingSink/write/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-hashing-sink/[non-jvm]write.html +$dokka.location:okio/HashingSink/write/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-hashing-sink/write.html $dokka.location:okio/HashingSource.Companion///PointingToDeclaration/okio/okio/-hashing-source/-companion/index.html $dokka.location:okio/HashingSource.Companion/hmacSha1/#okio.Source#okio.ByteString/PointingToDeclaration/okio/okio/-hashing-source/-companion/hmac-sha1.html $dokka.location:okio/HashingSource.Companion/hmacSha256/#okio.Source#okio.ByteString/PointingToDeclaration/okio/okio/-hashing-source/-companion/hmac-sha256.html @@ -452,13 +472,20 @@ $dokka.location:okio/HashingSource.Companion/sha512/#okio.Source/PointingToDecla $dokka.location:okio/HashingSource///PointingToDeclaration/okio/okio/-hashing-source/index.html $dokka.location:okio/HashingSource/close/#/PointingToDeclaration/okio/okio/-hashing-source/close.html $dokka.location:okio/HashingSource/hash/#/PointingToDeclaration/okio/okio/-hashing-source/hash.html -$dokka.location:okio/HashingSource/read/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-hashing-source/[non-jvm]read.html +$dokka.location:okio/HashingSource/read/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-hashing-source/read.html $dokka.location:okio/HashingSource/timeout/#/PointingToDeclaration/okio/okio/-hashing-source/timeout.html $dokka.location:okio/IOException///PointingToDeclaration/okio/okio/-i-o-exception/index.html +$dokka.location:okio/IOException/IOException/#/PointingToDeclaration/okio/okio/-i-o-exception/-i-o-exception.html $dokka.location:okio/IOException/IOException/#kotlin.String?#kotlin.Throwable?/PointingToDeclaration/okio/okio/-i-o-exception/-i-o-exception.html $dokka.location:okio/IOException/IOException/#kotlin.String?/PointingToDeclaration/okio/okio/-i-o-exception/-i-o-exception.html +$dokka.location:okio/Inflater///PointingToDeclaration/okio/okio/-inflater/index.html +$dokka.location:okio/Inflater/Inflater/#/PointingToDeclaration/okio/okio/-inflater/-inflater.html +$dokka.location:okio/Inflater/Inflater/#kotlin.Boolean/PointingToDeclaration/okio/okio/-inflater/-inflater.html +$dokka.location:okio/Inflater/end/#/PointingToDeclaration/okio/okio/-inflater/end.html +$dokka.location:okio/Inflater/getBytesWritten/#/PointingToDeclaration/okio/okio/-inflater/get-bytes-written.html $dokka.location:okio/InflaterSource///PointingToDeclaration/okio/okio/-inflater-source/index.html $dokka.location:okio/InflaterSource/InflaterSource/#okio.Source#java.util.zip.Inflater/PointingToDeclaration/okio/okio/-inflater-source/-inflater-source.html +$dokka.location:okio/InflaterSource/InflaterSource/#okio.Source#okio.Inflater/PointingToDeclaration/okio/okio/-inflater-source/-inflater-source.html $dokka.location:okio/InflaterSource/close/#/PointingToDeclaration/okio/okio/-inflater-source/close.html $dokka.location:okio/InflaterSource/read/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-inflater-source/read.html $dokka.location:okio/InflaterSource/readOrInflate/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-inflater-source/read-or-inflate.html @@ -518,6 +545,10 @@ $dokka.location:okio/Sink/close/#/PointingToDeclaration/okio/okio/-sink/close.h $dokka.location:okio/Sink/flush/#/PointingToDeclaration/okio/okio/-sink/flush.html $dokka.location:okio/Sink/timeout/#/PointingToDeclaration/okio/okio/-sink/timeout.html $dokka.location:okio/Sink/write/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-sink/write.html +$dokka.location:okio/Socket///PointingToDeclaration/okio/okio/-socket/index.html +$dokka.location:okio/Socket/cancel/#/PointingToDeclaration/okio/okio/-socket/cancel.html +$dokka.location:okio/Socket/sink/#/PointingToDeclaration/okio/okio/-socket/sink.html +$dokka.location:okio/Socket/source/#/PointingToDeclaration/okio/okio/-socket/source.html $dokka.location:okio/Source///PointingToDeclaration/okio/okio/-source/index.html $dokka.location:okio/Source/close/#/PointingToDeclaration/okio/okio/-source/close.html $dokka.location:okio/Source/read/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-source/read.html @@ -532,9 +563,12 @@ $dokka.location:okio/Throttler/source/#okio.Source/PointingToDeclaration/okio/o $dokka.location:okio/Timeout.Companion///PointingToDeclaration/okio/okio/-timeout/-companion/index.html $dokka.location:okio/Timeout.Companion/NONE/#/PointingToDeclaration/okio/okio/-timeout/-companion/-n-o-n-e.html $dokka.location:okio/Timeout.Companion/minTimeout/#kotlin.Long#kotlin.Long/PointingToDeclaration/okio/okio/-timeout/-companion/min-timeout.html +$dokka.location:okio/Timeout.Companion/timeout/okio.Timeout#kotlin.Long#kotlin.time.DurationUnit/PointingToDeclaration/okio/okio/-timeout/-companion/timeout.html +$dokka.location:okio/Timeout.Companion/timeout/okio.Timeout#kotlin.time.Duration/PointingToDeclaration/okio/okio/-timeout/-companion/timeout.html $dokka.location:okio/Timeout///PointingToDeclaration/okio/okio/-timeout/index.html $dokka.location:okio/Timeout/Timeout/#/PointingToDeclaration/okio/okio/-timeout/-timeout.html $dokka.location:okio/Timeout/awaitSignal/#java.util.concurrent.locks.Condition/PointingToDeclaration/okio/okio/-timeout/await-signal.html +$dokka.location:okio/Timeout/cancel/#/PointingToDeclaration/okio/okio/-timeout/cancel.html $dokka.location:okio/Timeout/clearDeadline/#/PointingToDeclaration/okio/okio/-timeout/clear-deadline.html $dokka.location:okio/Timeout/clearTimeout/#/PointingToDeclaration/okio/okio/-timeout/clear-timeout.html $dokka.location:okio/Timeout/deadline/#kotlin.Long#java.util.concurrent.TimeUnit/PointingToDeclaration/okio/okio/-timeout/deadline.html @@ -546,5 +580,10 @@ $dokka.location:okio/Timeout/throwIfReached/#/PointingToDeclaration/okio/okio/- $dokka.location:okio/Timeout/timeout/#kotlin.Long#java.util.concurrent.TimeUnit/PointingToDeclaration/okio/okio/-timeout/timeout.html $dokka.location:okio/Timeout/timeoutNanos/#/PointingToDeclaration/okio/okio/-timeout/timeout-nanos.html $dokka.location:okio/Timeout/waitUntilNotified/#kotlin.Any/PointingToDeclaration/okio/okio/-timeout/wait-until-notified.html +$dokka.location:okio/TypedOptions.Companion///PointingToDeclaration/okio/okio/-typed-options/-companion/index.html +$dokka.location:okio/TypedOptions.Companion/of/#kotlin.collections.Iterable[TypeParam(bounds=[kotlin.Any])]#kotlin.Function1[TypeParam(bounds=[kotlin.Any]),okio.ByteString]/PointingToDeclaration/okio/okio/-typed-options/-companion/of.html +$dokka.location:okio/TypedOptions///PointingToDeclaration/okio/okio/-typed-options/index.html +$dokka.location:okio/TypedOptions/TypedOptions/#kotlin.collections.List[TypeParam(bounds=[kotlin.Any])]#okio.Options/PointingToDeclaration/okio/okio/-typed-options/-typed-options.html +$dokka.location:okio/TypedOptions/get/#kotlin.Int/PointingToDeclaration/okio/okio/-typed-options/get.html +$dokka.location:okio/TypedOptions/size/#/PointingToDeclaration/okio/okio/-typed-options/size.html okio - diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/InstantSerializationTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/InstantSerializationTest.kt new file mode 100644 index 0000000000..2db0929db0 --- /dev/null +++ b/formats/json-tests/commonTest/src/kotlinx/serialization/InstantSerializationTest.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2025-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization + +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import kotlinx.serialization.builtins.* +import kotlin.time.* +import kotlin.test.* +import kotlin.reflect.typeOf + +@OptIn(ExperimentalTime::class) +class InstantSerializationTest: JsonTestBase() { + private fun iso8601Serialization(serializer: KSerializer) { + for ((instant, json) in listOf( + Pair(Instant.fromEpochSeconds(1607505416, 124000), + "\"2020-12-09T09:16:56.000124Z\""), + Pair(Instant.fromEpochSeconds(-1607505416, -124000), + "\"1919-01-23T14:43:03.999876Z\""), + Pair(Instant.fromEpochSeconds(987654321, 123456789), + "\"2001-04-19T04:25:21.123456789Z\""), + Pair(Instant.fromEpochSeconds(987654321, 0), + "\"2001-04-19T04:25:21Z\""), + )) { + assertJsonFormAndRestored(serializer, instant, json) + } + for ((instant, json) in listOf( + Pair(Instant.fromEpochSeconds(987654321, 123456789), + "\"2001-04-19T07:55:21.123456789+03:30\""), + Pair(Instant.fromEpochSeconds(987654321, 123456789), + "\"2001-04-19T00:55:21.123456789-03:30\""), + )) { + assertRestoredFromJsonForm(serializer, json, instant) + } + } + + private fun componentSerialization(serializer: KSerializer) { + for ((instant, json) in listOf( + Pair(Instant.fromEpochSeconds(1607505416, 124000), + "{\"epochSeconds\":1607505416,\"nanosecondsOfSecond\":124000}"), + Pair(Instant.fromEpochSeconds(-1607505416, -124000), + "{\"epochSeconds\":-1607505417,\"nanosecondsOfSecond\":999876000}"), + Pair(Instant.fromEpochSeconds(987654321, 123456789), + "{\"epochSeconds\":987654321,\"nanosecondsOfSecond\":123456789}"), + Pair(Instant.fromEpochSeconds(987654321, 0), + "{\"epochSeconds\":987654321,\"nanosecondsOfSecond\":0}"), + )) { + assertJsonFormAndRestored(serializer, instant, json) + } + // by default, `nanosecondsOfSecond` is optional + assertJsonFormAndRestored(serializer, Instant.fromEpochSeconds(987654321, 0), + "{\"epochSeconds\":987654321}", Json { }) + // having a `"nanosecondsOfSecond": 0` field doesn't break deserialization + assertEquals(Instant.fromEpochSeconds(987654321, 0), + Json.decodeFromString(serializer, + "{\"epochSeconds\":987654321,\"nanosecondsOfSecond\":0}")) + // with `default`, `nanosecondsOfSecond` is non-optional + assertJsonFormAndRestored(serializer, Instant.fromEpochSeconds(987654321, 0), + "{\"epochSeconds\":987654321,\"nanosecondsOfSecond\":0}") + // as does not having a `"nanosecondsOfSecond"` field if `encodeDefaults` is true + assertEquals(Instant.fromEpochSeconds(987654321, 0), + default.decodeFromString(serializer, + "{\"epochSeconds\":987654321}")) + // "epochSeconds" should always be present + assertFailsWith { Json.decodeFromString(serializer, "{}") } + assertFailsWith { Json.decodeFromString(serializer, "{\"nanosecondsOfSecond\":3}") } + } + + @Test + fun testIso8601Serialization() { + iso8601Serialization(Instant.serializer()) + } + + @Test + fun testComponentSerialization() { + componentSerialization(InstantComponentSerializer) + } + + @Test + fun testDefaultSerializers() { + // should be the same as the ISO 8601 + @Suppress("UNCHECKED_CAST") + iso8601Serialization(serializer(typeOf()) as KSerializer) + // iso8601Serialization(serializer()) TODO: uncomment when the compiler adds KT-75759 + } +} diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/JsonPathTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/JsonPathTest.kt index 8d31ba2273..10f943877a 100644 --- a/formats/json-tests/commonTest/src/kotlinx/serialization/JsonPathTest.kt +++ b/formats/json-tests/commonTest/src/kotlinx/serialization/JsonPathTest.kt @@ -156,6 +156,27 @@ class JsonPathTest : JsonTestBase() { expectPath(expectedPath) { json.decodeFromString(Sealed.serializer(), malformed) } } + @Serializable + data class SimpleNested(val n: SimpleNested? = null, val t: DataObject? = null) + + @Serializable + data object DataObject + + @Test + fun testMalformedDataObjectInDeeplyNestedStructure() { + var outer = SimpleNested(t = DataObject) + repeat(20) { + outer = SimpleNested(n = outer) + } + val str = Json.encodeToString(SimpleNested.serializer(), outer) + // throw-away data + Json.decodeFromString(SimpleNested.serializer(), str) + + val malformed = str.replace("{}", "42") + val expectedPath = "$" + ".n".repeat(20) + ".t\n" + expectPath(expectedPath) { Json.decodeFromString(SimpleNested.serializer(), malformed) } + } + private inline fun expectPath(path: String, block: () -> Unit) { val message = runCatching { block() } .exceptionOrNull()!!.message!! 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 de8cfb38b0..66f00177aa 100644 --- a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTestBase.kt +++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTestBase.kt @@ -190,4 +190,15 @@ abstract class JsonTestBase { assertTrue("Failed with streaming = $jsonTestingMode\n\tsource value =$data\n\tdeserialized value=$deserialized") { check(data, deserialized) } } } + + internal fun assertRestoredFromJsonForm( + serializer: KSerializer, + jsonForm: String, + expected: T, + ) { + parametrizedTest { jsonTestingMode -> + val deserialized: T = Json.decodeFromString(serializer, jsonForm, jsonTestingMode) + assertEquals(expected, deserialized, "Failed with streaming = $jsonTestingMode") + } + } } diff --git a/formats/json/api/kotlinx-serialization-json.api b/formats/json/api/kotlinx-serialization-json.api index d46439e0c3..48a747da2b 100644 --- a/formats/json/api/kotlinx-serialization-json.api +++ b/formats/json/api/kotlinx-serialization-json.api @@ -136,7 +136,7 @@ public abstract interface annotation class kotlinx/serialization/json/JsonClassD public abstract fun discriminator ()Ljava/lang/String; } -public synthetic class kotlinx/serialization/json/JsonClassDiscriminator$Impl : kotlinx/serialization/json/JsonClassDiscriminator { +public final synthetic class kotlinx/serialization/json/JsonClassDiscriminator$Impl : kotlinx/serialization/json/JsonClassDiscriminator { public fun (Ljava/lang/String;)V public final synthetic fun discriminator ()Ljava/lang/String; } @@ -265,7 +265,7 @@ public final class kotlinx/serialization/json/JsonEncoder$DefaultImpls { public abstract interface annotation class kotlinx/serialization/json/JsonIgnoreUnknownKeys : java/lang/annotation/Annotation { } -public synthetic class kotlinx/serialization/json/JsonIgnoreUnknownKeys$Impl : kotlinx/serialization/json/JsonIgnoreUnknownKeys { +public final synthetic class kotlinx/serialization/json/JsonIgnoreUnknownKeys$Impl : kotlinx/serialization/json/JsonIgnoreUnknownKeys { public fun ()V } @@ -278,7 +278,7 @@ public abstract interface annotation class kotlinx/serialization/json/JsonNames public abstract fun names ()[Ljava/lang/String; } -public synthetic class kotlinx/serialization/json/JsonNames$Impl : kotlinx/serialization/json/JsonNames { +public final synthetic class kotlinx/serialization/json/JsonNames$Impl : kotlinx/serialization/json/JsonNames { public fun ([Ljava/lang/String;)V public final synthetic fun names ()[Ljava/lang/String; } diff --git a/formats/json/build.gradle.kts b/formats/json/build.gradle.kts index db48d95564..2ef589ac19 100644 --- a/formats/json/build.gradle.kts +++ b/formats/json/build.gradle.kts @@ -52,11 +52,4 @@ kotlin { } } -// This task should be disabled because of no need to build and publish intermediate JsWasm sourceset -tasks.whenTaskAdded { - if (name == "compileJsWasmMainKotlinMetadata") { - enabled = false - } -} - configureJava9ModuleInfo() diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonPath.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonPath.kt index 14e70a4259..d6e3de1516 100644 --- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonPath.kt +++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonPath.kt @@ -134,7 +134,9 @@ internal class JsonPath { private fun resize() { val newSize = currentDepth * 2 currentObjectPath = currentObjectPath.copyOf(newSize) - indicies = indicies.copyOf(newSize) + val newIndices = IntArray(newSize) { -1 } + indicies.copyInto(newIndices) + indicies = newIndices } override fun toString(): String = getPath() diff --git a/formats/protobuf/api/kotlinx-serialization-protobuf.api b/formats/protobuf/api/kotlinx-serialization-protobuf.api index 682e4bf16b..59a1073056 100644 --- a/formats/protobuf/api/kotlinx-serialization-protobuf.api +++ b/formats/protobuf/api/kotlinx-serialization-protobuf.api @@ -34,7 +34,7 @@ public abstract interface annotation class kotlinx/serialization/protobuf/ProtoN public abstract fun number ()I } -public synthetic class kotlinx/serialization/protobuf/ProtoNumber$Impl : kotlinx/serialization/protobuf/ProtoNumber { +public final synthetic class kotlinx/serialization/protobuf/ProtoNumber$Impl : kotlinx/serialization/protobuf/ProtoNumber { public fun (I)V public final synthetic fun number ()I } @@ -42,14 +42,14 @@ public synthetic class kotlinx/serialization/protobuf/ProtoNumber$Impl : kotlinx public abstract interface annotation class kotlinx/serialization/protobuf/ProtoOneOf : java/lang/annotation/Annotation { } -public synthetic class kotlinx/serialization/protobuf/ProtoOneOf$Impl : kotlinx/serialization/protobuf/ProtoOneOf { +public final synthetic class kotlinx/serialization/protobuf/ProtoOneOf$Impl : kotlinx/serialization/protobuf/ProtoOneOf { public fun ()V } public abstract interface annotation class kotlinx/serialization/protobuf/ProtoPacked : java/lang/annotation/Annotation { } -public synthetic class kotlinx/serialization/protobuf/ProtoPacked$Impl : kotlinx/serialization/protobuf/ProtoPacked { +public final synthetic class kotlinx/serialization/protobuf/ProtoPacked$Impl : kotlinx/serialization/protobuf/ProtoPacked { public fun ()V } @@ -57,7 +57,7 @@ public abstract interface annotation class kotlinx/serialization/protobuf/ProtoT public abstract fun type ()Lkotlinx/serialization/protobuf/ProtoIntegerType; } -public synthetic class kotlinx/serialization/protobuf/ProtoType$Impl : kotlinx/serialization/protobuf/ProtoType { +public final synthetic class kotlinx/serialization/protobuf/ProtoType$Impl : kotlinx/serialization/protobuf/ProtoType { public fun (Lkotlinx/serialization/protobuf/ProtoIntegerType;)V public final synthetic fun type ()Lkotlinx/serialization/protobuf/ProtoIntegerType; } diff --git a/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufTaggedEncoder.kt b/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufTaggedEncoder.kt index 18be4e0a69..6ba93e9e7d 100644 --- a/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufTaggedEncoder.kt +++ b/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufTaggedEncoder.kt @@ -36,6 +36,7 @@ internal abstract class ProtobufTaggedEncoder : ProtobufTaggedBase(), Encoder, C public final override fun encodeNull() { if (nullableMode != NullableMode.ACCEPTABLE) { + @Suppress("REDUNDANT_ELSE_IN_WHEN") val message = when (nullableMode) { NullableMode.OPTIONAL -> "'null' is not supported for optional properties in ProtoBuf" NullableMode.COLLECTION -> "'null' is not supported as the value of collection types in ProtoBuf" diff --git a/gradle.properties b/gradle.properties index deb1585fa6..092c559a8f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,11 +3,11 @@ # group=org.jetbrains.kotlinx -version=1.8.2-SNAPSHOT +version=1.9.1-SNAPSHOT jdk_toolchain_version=11 # This version takes precedence if 'bootstrap' property passed to project -kotlin.version.snapshot=2.1.255-SNAPSHOT +kotlin.version.snapshot=2.2.255-SNAPSHOT # Also set kotlin.native.home to your $kotlin_project$/kotlin-native/dist if you want to use snapshot Native #kotlin.native.home= @@ -23,3 +23,6 @@ org.gradle.kotlin.dsl.allWarningsAsErrors=true kotlin.native.distribution.type=prebuilt org.gradle.jvmargs="-XX:+HeapDumpOnOutOfMemoryError" + +org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled +org.jetbrains.dokka.experimental.gradle.pluginMode.nowarn=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 132a67b614..c9d8fd26a1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "2.1.20" +kotlin = "2.2.0" kover = "0.8.2" dokka = "2.0.0" knit = "0.5.0" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 381baa9cef..d7c3e63751 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://cache-redirector.jetbrains.com/services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/integration-test/build.gradle.kts b/integration-test/build.gradle.kts index 64b44e6d9f..6ce87104e8 100644 --- a/integration-test/build.gradle.kts +++ b/integration-test/build.gradle.kts @@ -20,7 +20,7 @@ plugins { repositories { mavenCentral() - maven("https://cache-redirector.jetbrains.com/maven.pkg.jetbrains.space/kotlin/p/kotlin/dev") + maven("https://redirector.kotlinlang.org/maven/dev") mavenLocal { mavenContent { snapshotsOnly() diff --git a/integration-test/gradle.properties b/integration-test/gradle.properties index 3b4f9014ab..2d2f12686e 100644 --- a/integration-test/gradle.properties +++ b/integration-test/gradle.properties @@ -2,8 +2,8 @@ # Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. # -mainKotlinVersion=2.1.20 -mainLibVersion=1.8.2-SNAPSHOT +mainKotlinVersion=2.2.0 +mainLibVersion=1.9.1-SNAPSHOT kotlin.code.style=official diff --git a/integration-test/gradle/wrapper/gradle-wrapper.properties b/integration-test/gradle/wrapper/gradle-wrapper.properties index b82aa23a4f..371de143b6 100644 --- a/integration-test/gradle/wrapper/gradle-wrapper.properties +++ b/integration-test/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://cache-redirector.jetbrains.com/services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/integration-test/kotlin-js-store/yarn.lock b/integration-test/kotlin-js-store/yarn.lock index c44683dfd5..02b202d90e 100644 --- a/integration-test/kotlin-js-store/yarn.lock +++ b/integration-test/kotlin-js-store/yarn.lock @@ -461,11 +461,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -typescript@5.5.4: - version "5.5.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" - integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== - workerpool@^6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" diff --git a/integration-test/settings.gradle.kts b/integration-test/settings.gradle.kts index c2cb0c46f7..3d37c444fc 100644 --- a/integration-test/settings.gradle.kts +++ b/integration-test/settings.gradle.kts @@ -17,7 +17,7 @@ pluginManagement { repositories { mavenCentral() maven("https://plugins.gradle.org/m2/") - maven("https://cache-redirector.jetbrains.com/maven.pkg.jetbrains.space/kotlin/p/kotlin/dev") + maven("https://redirector.kotlinlang.org/maven/dev") mavenLocal() } } diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock index 2608a773ca..c90cd890a2 100644 --- a/kotlin-js-store/yarn.lock +++ b/kotlin-js-store/yarn.lock @@ -456,11 +456,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -typescript@5.5.4: - version "5.5.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" - integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== - workerpool@^6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" diff --git a/settings.gradle.kts b/settings.gradle.kts index 5d8194dd35..6b2166dadf 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,11 +25,7 @@ pluginManagement { } // kotlin-dev with space redirector - maven("https://cache-redirector.jetbrains.com/maven.pkg.jetbrains.space/kotlin/p/kotlin/dev") - - maven("https://maven.pkg.jetbrains.space/kotlin/p/dokka/dev") - // For Dokka that depends on kotlinx-html - maven("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven") + maven("https://redirector.kotlinlang.org/maven/dev") gradlePluginPortal() mavenCentral()