Skip to content
Merged
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
4 changes: 2 additions & 2 deletions core/api/kotlinx-serialization-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -1067,7 +1067,7 @@ public abstract class kotlinx/serialization/internal/TaggedDecoder : kotlinx/ser
public final fun decodeEnum (Lkotlinx/serialization/descriptors/SerialDescriptor;)I
public final fun decodeFloat ()F
public final fun decodeFloatElement (Lkotlinx/serialization/descriptors/SerialDescriptor;I)F
public final fun decodeInline (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/Decoder;
public fun decodeInline (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/Decoder;
public final fun decodeInlineElement (Lkotlinx/serialization/descriptors/SerialDescriptor;I)Lkotlinx/serialization/encoding/Decoder;
public final fun decodeInt ()I
public final fun decodeIntElement (Lkotlinx/serialization/descriptors/SerialDescriptor;I)I
Expand Down Expand Up @@ -1123,7 +1123,7 @@ public abstract class kotlinx/serialization/internal/TaggedEncoder : kotlinx/ser
public final fun encodeEnum (Lkotlinx/serialization/descriptors/SerialDescriptor;I)V
public final fun encodeFloat (F)V
public final fun encodeFloatElement (Lkotlinx/serialization/descriptors/SerialDescriptor;IF)V
public final fun encodeInline (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/Encoder;
public fun encodeInline (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/Encoder;
public final fun encodeInlineElement (Lkotlinx/serialization/descriptors/SerialDescriptor;I)Lkotlinx/serialization/encoding/Encoder;
public final fun encodeInt (I)V
public final fun encodeIntElement (Lkotlinx/serialization/descriptors/SerialDescriptor;II)V
Expand Down
4 changes: 2 additions & 2 deletions core/commonMain/src/kotlinx/serialization/internal/Tagged.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public abstract class TaggedEncoder<Tag : Any?> : Encoder, CompositeEncoder {
protected open fun encodeTaggedInline(tag: Tag, inlineDescriptor: SerialDescriptor): Encoder =
this.apply { pushTag(tag) }

final override fun encodeInline(descriptor: SerialDescriptor): Encoder =
override fun encodeInline(descriptor: SerialDescriptor): Encoder =
encodeTaggedInline(popTag(), descriptor)

// ---- Implementation of low-level API ----
Expand Down Expand Up @@ -209,7 +209,7 @@ public abstract class TaggedDecoder<Tag : Any?> : Decoder, CompositeDecoder {

// ---- Implementation of low-level API ----

final override fun decodeInline(descriptor: SerialDescriptor): Decoder =
override fun decodeInline(descriptor: SerialDescriptor): Decoder =
decodeTaggedInline(popTag(), descriptor)

// TODO this method should be overridden by any sane format that supports top-level nulls
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ data class SimpleContainerForUInt(val i: UInt)
@JvmInline
value class MyUInt(val m: Int)

object MyUIntSerializer: KSerializer<MyUInt> {
object MyUIntSerializer : KSerializer<MyUInt> {
override val descriptor = UInt.serializer().descriptor
override fun serialize(encoder: Encoder, value: MyUInt) {
encoder.encodeInline(descriptor).encodeInt(value.m)
Expand Down Expand Up @@ -73,12 +73,45 @@ value class ResourceKind(val kind: SampleEnum)
@Serializable
data class ResourceIdentifier(val id: ResourceId, val type: ResourceType, val type2: ValueWrapper)

@Serializable @JvmInline
@Serializable
@JvmInline
value class ValueWrapper(val wrapped: ResourceType)

@Serializable
@JvmInline
value class Outer(val inner: Inner)

@Serializable
data class Inner(val n: Int)

@Serializable
data class OuterOuter(val outer: Outer)

@Serializable
@JvmInline
value class WithList(val value: List<Int>)

class InlineClassesTest : JsonTestBase() {
private val precedent: UInt = Int.MAX_VALUE.toUInt() + 10.toUInt()

@Test
fun withList() = noLegacyJs {
val withList = WithList(listOf(1, 2, 3))
assertJsonFormAndRestored(WithList.serializer(), withList, """[1,2,3]""")
}

@Test
fun testOuterInner() = noLegacyJs {
val o = Outer(Inner(10))
assertJsonFormAndRestored(Outer.serializer(), o, """{"n":10}""")
}

@Test
fun testOuterOuterInner() = noLegacyJs {
val o = OuterOuter(Outer(Inner(10)))
assertJsonFormAndRestored(OuterOuter.serializer(), o, """{"outer":{"n":10}}""")
}

@Test
fun testTopLevel() = noLegacyJs {
assertJsonFormAndRestored(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.serialization.features.inline

import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.test.*
import kotlin.jvm.*
import kotlin.test.*

class ValueClassesInSealedHierarchyTest : JsonTestBase() {
@Test
fun testSingle() = noLegacyJs {
val single = "foo"
assertJsonFormAndRestored(
AnyValue.serializer(),
AnyValue.Single(single),
"\"$single\""
)
}

@Test
fun testComplex() = noLegacyJs {
val complexJson = """{"id":"1","name":"object"}"""
assertJsonFormAndRestored(
AnyValue.serializer(),
AnyValue.Complex(mapOf("id" to "1", "name" to "object")),
complexJson
)
}

@Test
fun testMulti() = noLegacyJs {
val multiJson = """["list","of","strings"]"""
assertJsonFormAndRestored(AnyValue.serializer(), AnyValue.Multi(listOf("list", "of", "strings")), multiJson)
}
}


// From https://github.com/Kotlin/kotlinx.serialization/issues/2159
@Serializable(with = AnyValue.Companion.Serializer::class)
sealed interface AnyValue {

@JvmInline
@Serializable
value class Single(val value: String) : AnyValue

@JvmInline
@Serializable
value class Multi(val values: List<String>) : AnyValue

@JvmInline
@Serializable
value class Complex(val values: Map<String, String>) : AnyValue

@JvmInline
@Serializable
value class Unknown(val value: JsonElement) : AnyValue

companion object {
object Serializer : JsonContentPolymorphicSerializer<AnyValue>(AnyValue::class) {

override fun selectDeserializer(element: JsonElement): DeserializationStrategy<AnyValue> =
when {
element is JsonArray && element.all { it is JsonPrimitive && it.isString } -> Multi.serializer()
element is JsonObject && element.values.all { it is JsonPrimitive && it.isString } -> Complex.serializer()
element is JsonPrimitive && element.isString -> Single.serializer()
else -> Unknown.serializer()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,14 @@ private sealed class AbstractJsonTreeDecoder(
override fun decodeTaggedInline(tag: String, inlineDescriptor: SerialDescriptor): Decoder =
if (inlineDescriptor.isUnsignedNumber) JsonDecoderForUnsignedTypes(StringJsonLexer(getPrimitiveValue(tag).content), json)
else super.decodeTaggedInline(tag, inlineDescriptor)

override fun decodeInline(descriptor: SerialDescriptor): Decoder {
return if (currentTagOrNull != null) super.decodeInline(descriptor)
else JsonPrimitiveDecoder(json, value).decodeInline(descriptor)
}
}

private class JsonPrimitiveDecoder(json: Json, override val value: JsonPrimitive) : AbstractJsonTreeDecoder(json, value) {
private class JsonPrimitiveDecoder(json: Json, override val value: JsonElement) : AbstractJsonTreeDecoder(json, value) {

init {
pushTag(PRIMITIVE_TAG)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public fun <T> Json.writeJson(value: T, serializer: SerializationStrategy<T>): J
@ExperimentalSerializationApi
private sealed class AbstractJsonTreeEncoder(
final override val json: Json,
private val nodeConsumer: (JsonElement) -> Unit
protected val nodeConsumer: (JsonElement) -> Unit
) : NamedValueEncoder(), JsonEncoder {

final override val serializersModule: SerializersModule
Expand Down Expand Up @@ -80,7 +80,6 @@ private sealed class AbstractJsonTreeEncoder(
encodePolymorphically(serializer, value) { polymorphicDiscriminator = it }
} else JsonPrimitiveEncoder(json, nodeConsumer).apply {
encodeSerializableValue(serializer, value)
endEncode(serializer.descriptor)
}
}

Expand Down Expand Up @@ -112,6 +111,11 @@ private sealed class AbstractJsonTreeEncoder(
else -> super.encodeTaggedInline(tag, inlineDescriptor)
}

override fun encodeInline(descriptor: SerialDescriptor): Encoder {
return if (currentTagOrNull != null) super.encodeInline(descriptor)
else JsonPrimitiveEncoder(json, nodeConsumer).encodeInline(descriptor)
}

@SuppressAnimalSniffer // Long(Integer).toUnsignedString(long)
private fun inlineUnsignedNumberEncoder(tag: String) = object : AbstractEncoder() {
override val serializersModule: SerializersModule = json.serializersModule
Expand Down Expand Up @@ -176,6 +180,7 @@ private class JsonPrimitiveEncoder(
require(key === PRIMITIVE_TAG) { "This output can only consume primitives with '$PRIMITIVE_TAG' tag" }
require(content == null) { "Primitive element was already recorded. Does call to .encodeXxx happen more than once?" }
content = element
nodeConsumer(element)
}

override fun getCurrent(): JsonElement =
Expand Down