Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions buildSrc/src/main/kotlin/source-sets-conventions.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ kotlin {
progressiveMode = true

optIn("kotlin.ExperimentalMultiplatform")
optIn("kotlin.ExperimentalSubclassOptIn")
optIn("kotlin.ExperimentalStdlibApi")
optIn("kotlinx.serialization.InternalSerializationApi")
optIn("kotlinx.serialization.SealedSerializationApi")
}
}

Expand Down Expand Up @@ -147,7 +150,9 @@ kotlin {
sourceSets.matching({ it.name.contains("Test") }).configureEach {
languageSettings {
optIn("kotlinx.serialization.InternalSerializationApi")
optIn("kotlinx.serialization.SealedSerializationApi")
optIn("kotlinx.serialization.ExperimentalSerializationApi")
optIn("kotlinx.serialization.encoding.AdvancedEncodingApi")
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions core/api/kotlinx-serialization-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ public final class kotlinx/serialization/SealedClassSerializer : kotlinx/seriali
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
}

public abstract interface annotation class kotlinx/serialization/SealedSerializationApi : java/lang/annotation/Annotation {
}

public abstract interface class kotlinx/serialization/SerialFormat {
public abstract fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule;
}
Expand Down Expand Up @@ -424,6 +427,9 @@ public abstract class kotlinx/serialization/encoding/AbstractEncoder : kotlinx/s
public fun shouldEncodeElementDefault (Lkotlinx/serialization/descriptors/SerialDescriptor;I)Z
}

public abstract interface annotation class kotlinx/serialization/encoding/AdvancedEncodingApi : java/lang/annotation/Annotation {
}

public abstract interface class kotlinx/serialization/encoding/ChunkedDecoder {
public abstract fun decodeStringChunked (Lkotlin/jvm/functions/Function1;)V
}
Expand Down
8 changes: 8 additions & 0 deletions core/api/kotlinx-serialization-core.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
// - Show declarations: true

// Library unique name: <org.jetbrains.kotlinx:kotlinx-serialization-core>
open annotation class kotlinx.serialization.encoding/AdvancedEncodingApi : kotlin/Annotation { // kotlinx.serialization.encoding/AdvancedEncodingApi|null[0]
constructor <init>() // kotlinx.serialization.encoding/AdvancedEncodingApi.<init>|<init>(){}[0]
}

open annotation class kotlinx.serialization.internal/NamedCompanion : kotlin/Annotation { // kotlinx.serialization.internal/NamedCompanion|null[0]
constructor <init>() // kotlinx.serialization.internal/NamedCompanion.<init>|<init>(){}[0]
}
Expand Down Expand Up @@ -61,6 +65,10 @@ open annotation class kotlinx.serialization/Required : kotlin/Annotation { // ko
constructor <init>() // kotlinx.serialization/Required.<init>|<init>(){}[0]
}

open annotation class kotlinx.serialization/SealedSerializationApi : kotlin/Annotation { // kotlinx.serialization/SealedSerializationApi|null[0]
constructor <init>() // kotlinx.serialization/SealedSerializationApi.<init>|<init>(){}[0]
}

open annotation class kotlinx.serialization/SerialInfo : kotlin/Annotation { // kotlinx.serialization/SerialInfo|null[0]
constructor <init>() // kotlinx.serialization/SerialInfo.<init>|<init>(){}[0]
}
Expand Down
14 changes: 14 additions & 0 deletions core/commonMain/src/kotlinx/serialization/Annotations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -377,3 +377,17 @@ public annotation class ExperimentalSerializationApi
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS)
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
public annotation class InternalSerializationApi

/**
* Marks interfaces and non-final classes that can be freely referenced in users' code but should not be
* implemented or inherited. Such declarations are effectively `sealed` and do not have this modifier purely for technical reasons.
*
* kotlinx.serialization library provides compatibility guarantees for existing signatures of such classes;
* however, new functions or properties can be added to them in any release.
*/
@MustBeDocumented
@Target(AnnotationTarget.CLASS)
@RequiresOptIn(message = "This class or interface should not be inherited/implemented outside of kotlinx.serialization library. " +
"Note it is still permitted to use it directly. Read its documentation about inheritance for details.", level = RequiresOptIn.Level.ERROR)
public annotation class SealedSerializationApi

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ import kotlin.reflect.*
* }
* ```
*/
@Suppress("FunctionName")
@OptIn(ExperimentalSerializationApi::class)
public fun buildClassSerialDescriptor(
serialName: String,
vararg typeParameters: SerialDescriptor,
Expand All @@ -69,7 +67,7 @@ public fun buildClassSerialDescriptor(
}

/**
* Factory to create a trivial primitive descriptors.
* Factory to create trivial primitive descriptors. [serialName] must be non-blank and unique.
* Primitive descriptors should be used when the serialized form of the data has a primitive form, for example:
* ```
* object LongAsStringSerializer : KSerializer<Long> {
Expand All @@ -86,16 +84,16 @@ public fun buildClassSerialDescriptor(
* }
* ```
*/
@Suppress("FunctionName")
public fun PrimitiveSerialDescriptor(serialName: String, kind: PrimitiveKind): SerialDescriptor {
require(serialName.isNotBlank()) { "Blank serial names are prohibited" }
return PrimitiveDescriptorSafe(serialName, kind)
}

/**
* Factory to create a new descriptor that is identical to [original] except that the name is equal to [serialName].
* Should be used when you want to serialize a type as another non-primitive type.
* Don't use this if you want to serialize a type as a primitive value, use [PrimitiveSerialDescriptor] instead.
*
* Usually used when you want to serialize a type as another type, delegating implementation of `serialize` and `deserialize`.
*
* Example:
* ```
* @Serializable(CustomSerializer::class)
Expand All @@ -115,27 +113,24 @@ public fun PrimitiveSerialDescriptor(serialName: String, kind: PrimitiveKind): S
* }
* ```
*/
@ExperimentalSerializationApi
public fun SerialDescriptor(serialName: String, original: SerialDescriptor): SerialDescriptor {
require(serialName.isNotBlank()) { "Blank serial names are prohibited" }
require(original.kind !is PrimitiveKind) { "For primitive descriptors please use 'PrimitiveSerialDescriptor' instead" }
require(serialName != original.serialName) { "The name of the wrapped descriptor ($serialName) cannot be the same as the name of the original descriptor (${original.serialName})" }

if (original.kind is PrimitiveKind) checkNameIsNotAPrimitive(serialName)

return WrappedSerialDescriptor(serialName, original)
}

@OptIn(ExperimentalSerializationApi::class)
internal class WrappedSerialDescriptor(override val serialName: String, original: SerialDescriptor) : SerialDescriptor by original

/**
* An unsafe alternative to [buildClassSerialDescriptor] that supports an arbitrary [SerialKind].
* This function is left public only for migration of pre-release users and is not intended to be used
* as generally-safe and stable mechanism. Beware that it can produce inconsistent or non spec-compliant instances.
* as a generally safe and stable mechanism. Beware that it can produce inconsistent or non-spec-compliant instances.
*
* If you end up using this builder, please file an issue with your use-case in kotlinx.serialization issue tracker.
* If you end up using this builder, please file an issue with your use-case to the kotlinx.serialization issue tracker.
*/
@InternalSerializationApi
@OptIn(ExperimentalSerializationApi::class)
public fun buildSerialDescriptor(
serialName: String,
kind: SerialKind,
Expand All @@ -152,14 +147,32 @@ public fun buildSerialDescriptor(

/**
* Retrieves descriptor of type [T] using reified [serializer] function.
*
* Example:
* ```
* serialDescriptor<List<String>>() // Returns kotlin.collections.ArrayList(PrimitiveDescriptor(kotlin.String))
* ```
*/
public inline fun <reified T> serialDescriptor(): SerialDescriptor = serializer<T>().descriptor

/**
* Retrieves descriptor of type associated with the given [KType][type]
* Retrieves descriptor of a type associated with the given [KType][type].
*
* Example:
* ```
* val type = typeOf<List<String>>()
*
* serialDescriptor(type) // Returns kotlin.collections.ArrayList(PrimitiveDescriptor(kotlin.String))
* ```
*/
public fun serialDescriptor(type: KType): SerialDescriptor = serializer(type).descriptor

/* The rest of the functions intentionally left experimental for later stabilization
It is unclear whether they should be left as-is,
or moved to ClassSerialDescriptorBuilder (because this is the main place for them to be used),
or simply deprecated in favor of ListSerializer(Element.serializer()).descriptor
*/

/**
* Creates a descriptor for the type `List<T>` where `T` is the type associated with [elementDescriptor].
*/
Expand Down Expand Up @@ -227,9 +240,10 @@ public val SerialDescriptor.nullable: SerialDescriptor
* Returns non-nullable serial descriptor for the type if this descriptor has been auto-generated (plugin
* generated descriptors) or created with `.nullable` extension on a descriptor or serializer.
*
* Otherwise, returns this.
* Otherwise, returns `this`.
*
* It may return nullable descriptor if this descriptor has been created manually as nullable by directly implementing SerialDescriptor interface.
* It may return a nullable descriptor
* if `this` descriptor has been created manually as nullable by directly implementing SerialDescriptor interface.
*
* @see SerialDescriptor.nullable
* @see KSerializer.nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import kotlinx.serialization.modules.*
* as a single `Long` value, its descriptor should have [PrimitiveKind.LONG] without nested elements even though the class itself
* represents a structure with two primitive fields.
*/
@ExperimentalSerializationApi
public sealed class SerialKind {

/**
Expand All @@ -37,7 +36,6 @@ public sealed class SerialKind {
*
* Corresponding encoder and decoder methods are [Encoder.encodeEnum] and [Decoder.decodeEnum].
*/
@ExperimentalSerializationApi
public object ENUM : SerialKind()

/**
Expand All @@ -50,7 +48,6 @@ public sealed class SerialKind {
* However, if possible options are known statically (e.g. for sealed classes), they can be
* enumerated in child descriptors similarly to [ENUM].
*/
@ExperimentalSerializationApi
public object CONTEXTUAL : SerialKind()

override fun toString(): String {
Expand Down Expand Up @@ -85,7 +82,6 @@ public sealed class SerialKind {
* For the `Color` example, represented as single [Int], its descriptor should have [INT] kind, zero elements and serial name **not equals**
* to `kotlin.Int`: `PrimitiveDescriptor("my.package.ColorAsInt", PrimitiveKind.INT)`
*/
@OptIn(ExperimentalSerializationApi::class) // May be @Experimental, but break clients + makes impossible to use stable PrimitiveSerialDescriptor
public sealed class PrimitiveKind : SerialKind() {
/**
* Primitive kind that represents a boolean `true`/`false` value.
Expand Down Expand Up @@ -188,7 +184,6 @@ public sealed class PrimitiveKind : SerialKind() {
* For example, provided serializer for [Map.Entry] represents it as [Map] type, so it is serialized
* as `{"actualKey": "actualValue"}` map directly instead of `{"key": "actualKey", "value": "actualValue"}`
*/
@ExperimentalSerializationApi
public sealed class StructureKind : SerialKind() {

/**
Expand Down Expand Up @@ -239,7 +234,7 @@ public sealed class StructureKind : SerialKind() {
* bounded and sealed polymorphism common property: not knowing the actual type statically and requiring
* formats to additionally encode it.
*/
@ExperimentalSerializationApi
@ExperimentalSerializationApi // Intentionally left experimental to sort out things with buildSerialDescriptor(PolymorphicKind.SEALED)
public sealed class PolymorphicKind : SerialKind() {
/**
* Sealed kind represents Kotlin sealed classes, where all subclasses are known statically at the moment of declaration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import kotlinx.serialization.descriptors.*
* See [Decoder] documentation for information about each particular `decode*` method.
*/
@ExperimentalSerializationApi
@SubclassOptInRequired(AdvancedEncodingApi::class)
public abstract class AbstractDecoder : Decoder, CompositeDecoder {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import kotlinx.serialization.internal.*
* See [Encoder] documentation for information about each particular `encode*` method.
*/
@ExperimentalSerializationApi
@SubclassOptInRequired(AdvancedEncodingApi::class)
public abstract class AbstractEncoder : Encoder, CompositeEncoder {

override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder = this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ import kotlinx.serialization.modules.*
* `Decoder` interface is not stable for inheritance in 3rd-party libraries, as new methods
* might be added to this interface or contracts of the existing methods can be changed.
*/
@SubclassOptInRequired(AdvancedEncodingApi::class)
public interface Decoder {
/**
* Context of the current serialization process, including contextual and polymorphic serialization and,
Expand Down Expand Up @@ -292,6 +293,7 @@ internal inline fun <T : Any> Decoder.decodeIfNullable(deserializer: Deserializa
* `CompositeDecoder` interface is not stable for inheritance in 3rd party libraries, as new methods
* might be added to this interface or contracts of the existing methods can be changed.
*/
@SubclassOptInRequired(AdvancedEncodingApi::class)
public interface CompositeDecoder {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ import kotlinx.serialization.modules.*
* `Encoder` interface is not stable for inheritance in 3rd party libraries, as new methods
* might be added to this interface or contracts of the existing methods can be changed.
*/
@SubclassOptInRequired(AdvancedEncodingApi::class)
public interface Encoder {
/**
* Context of the current serialization process, including contextual and polymorphic serialization and,
Expand Down Expand Up @@ -320,6 +321,7 @@ public interface Encoder {
* `CompositeEncoder` interface is not stable for inheritance in 3rd party libraries, as new methods
* might be added to this interface or contracts of the existing methods can be changed.
*/
@SubclassOptInRequired(AdvancedEncodingApi::class)
public interface CompositeEncoder {
/**
* Context of the current serialization process, including contextual and polymorphic serialization and,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.serialization.encoding

/**
* Marks all encoding- and decoding-related interfaces in kotlinx.serialization.
* These interfaces are used in serializers and have to be implemented only if you want to write
* a custom serialization format. Since encoder/decoder invariants are quite complex,
* it is recommended to start with reading their documentation: see [Encoder] and [Decoder],
* and [kotlinx.serialization guide](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/formats.md#custom-formats-experimental) about them.
* There are also existing skeleton implementations that you may find useful: [AbstractEncoder] and [AbstractDecoder].
*/
@RequiresOptIn(
"You should implement Encoder or Decoder only if you want to write a custom kotlinx.serialization format. " +
"Before doing so, please consult official guide at https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/formats.md#custom-formats-experimental",
level = RequiresOptIn.Level.WARNING
)
public annotation class AdvancedEncodingApi
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import kotlinx.serialization.modules.*
/**
* Encoder that does not do any operations. Its main purpose is to ignore data instead of writing it.
*/
@OptIn(ExperimentalSerializationApi::class)
@OptIn(ExperimentalSerializationApi::class, AdvancedEncodingApi::class)
internal object NoOpEncoder : AbstractEncoder() {
override val serializersModule: SerializersModule = EmptySerializersModule()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,15 @@ internal class PrimitiveSerialDescriptor(
return false
}
override fun hashCode() = serialName.hashCode() + 31 * kind.hashCode()
private fun error(): Nothing = throw IllegalStateException("Primitive descriptor does not have elements")
private fun error(): Nothing = throw IllegalStateException("Primitive descriptor $serialName does not have elements")
}

internal fun PrimitiveDescriptorSafe(serialName: String, kind: PrimitiveKind): SerialDescriptor {
checkName(serialName)
checkNameIsNotAPrimitive(serialName)
return PrimitiveSerialDescriptor(serialName, kind)
}

private fun checkName(serialName: String) {
internal fun checkNameIsNotAPrimitive(serialName: String) {
val keys = BUILTIN_SERIALIZERS.keys
for (primitive in keys) {
val simpleName = primitive.simpleName!!.capitalize()
Expand Down
2 changes: 2 additions & 0 deletions core/commonMain/src/kotlinx/serialization/internal/Tagged.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import kotlinx.serialization.modules.*
* They neither do have stable API, nor internal invariants and are changed without any warnings.
*/
@InternalSerializationApi
@OptIn(AdvancedEncodingApi::class)
public abstract class TaggedEncoder<Tag : Any?> : Encoder, CompositeEncoder {

/**
Expand Down Expand Up @@ -176,6 +177,7 @@ public abstract class NamedValueEncoder : TaggedEncoder<String>() {
}

@InternalSerializationApi
@OptIn(AdvancedEncodingApi::class)
public abstract class TaggedDecoder<Tag : Any?> : Decoder, CompositeDecoder {
override val serializersModule: SerializersModule
get() = EmptySerializersModule()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,21 @@ class WrappedSerialDescriptorTest {
fun testWrappedComplexClass() {
checkWrapped(ComplexType.serializer().descriptor, "WrappedComplexType")
}
}

@Test
fun testWrappedPrimitive() {
checkWrapped(Int.serializer().descriptor, "MyInt")
}

@Test
fun testWrappedPrimitiveContract() {
assertFails { SerialDescriptor(" ", ComplexType.serializer().descriptor) }
assertFails {
SerialDescriptor(
SimpleType.serializer().descriptor.serialName,
SimpleType.serializer().descriptor
)
}
assertFails { SerialDescriptor("kotlin.Int", Int.serializer().descriptor) }
}
}
Loading