Skip to content

Commit

Permalink
Properly handle top-level value classes in encodeToJsonElement (#1777)
Browse files Browse the repository at this point in the history
Fixes #1774
  • Loading branch information
sandwwraith authored Dec 15, 2021
1 parent 1b2344f commit a33ef02
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:OptIn(ExperimentalSerializationApi::class)

Expand Down Expand Up @@ -69,7 +69,7 @@ private sealed class AbstractJsonTreeEncoder(

override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) {
// Writing non-structured data (i.e. primitives) on top-level (e.g. without any tag) requires special output
if (currentTagOrNull != null || serializer.descriptor.kind !is PrimitiveKind && serializer.descriptor.kind !== SerialKind.ENUM) {
if (currentTagOrNull != null || !serializer.descriptor.carrierDescriptor(serializersModule).requiresTopLevelTag) {
encodePolymorphically(serializer, value) { polymorphicDiscriminator = it }
} else JsonPrimitiveEncoder(json, nodeConsumer).apply {
encodeSerializableValue(serializer, value)
Expand Down Expand Up @@ -139,6 +139,9 @@ private sealed class AbstractJsonTreeEncoder(
}
}

private val SerialDescriptor.requiresTopLevelTag: Boolean
get() = kind is PrimitiveKind || kind === SerialKind.ENUM

internal const val PRIMITIVE_TAG = "primitive" // also used in JsonPrimitiveInput

private class JsonPrimitiveEncoder(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
/*
* Copyright 2017-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:OptIn(ExperimentalSerializationApi::class)

package kotlinx.serialization.json.internal

import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import kotlin.jvm.JvmField
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*
import kotlin.jvm.*

internal enum class WriteMode(@JvmField val begin: Char, @JvmField val end: Char) {
OBJ(BEGIN_OBJ, END_OBJ),
Expand Down Expand Up @@ -47,6 +47,6 @@ internal inline fun <T, R1 : T, R2 : T> Json.selectMapMode(

internal fun SerialDescriptor.carrierDescriptor(module: SerializersModule): SerialDescriptor = when {
kind == SerialKind.CONTEXTUAL -> module.getContextualDescriptor(this)?.carrierDescriptor(module) ?: this
isInline -> getElementDescriptor(0)
isInline -> getElementDescriptor(0).carrierDescriptor(module)
else -> this
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*
import kotlinx.serialization.test.*
import kotlin.jvm.*
import kotlin.test.*
Expand Down Expand Up @@ -72,11 +73,60 @@ value class ResourceId(val id: String)
value class ResourceType(val type: String)

@Serializable
data class ResourceIdentifier(val id: ResourceId, val type: ResourceType)
@JvmInline
value class ResourceKind(val kind: SampleEnum)

@Serializable
data class ResourceIdentifier(val id: ResourceId, val type: ResourceType, val type2: ValueWrapper)

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

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

@Test
fun testTopLevel() = noLegacyJs {
assertJsonFormAndRestored(
ResourceType.serializer(),
ResourceType("foo"),
""""foo"""",
)
}

@Test
fun testTopLevelOverEnum() = noLegacyJs {
assertJsonFormAndRestored(
ResourceKind.serializer(),
ResourceKind(SampleEnum.OptionC),
""""OptionC"""",
)
}

@Test
fun testTopLevelWrapper() = noLegacyJs {
assertJsonFormAndRestored(
ValueWrapper.serializer(),
ValueWrapper(ResourceType("foo")),
""""foo"""",
)
}

@Test
fun testTopLevelContextual() = noLegacyJs {
val module = SerializersModule {
contextual<ResourceType>(ResourceType.serializer())
}
val json = Json(default) { serializersModule = module }
assertJsonFormAndRestored(
ContextualSerializer(ResourceType::class),
ResourceType("foo"),
""""foo"""",
json
)
}


@Test
fun testSimpleContainer() = noLegacyJs {
assertJsonFormAndRestored(
Expand Down Expand Up @@ -106,8 +156,8 @@ class InlineClassesTest : JsonTestBase() {
fun testInlineClassesWithStrings() = noLegacyJs {
assertJsonFormAndRestored(
ResourceIdentifier.serializer(),
ResourceIdentifier(ResourceId("resId"), ResourceType("resType")),
"""{"id":"resId","type":"resType"}"""
ResourceIdentifier(ResourceId("resId"), ResourceType("resType"), ValueWrapper(ResourceType("wrappedType"))),
"""{"id":"resId","type":"resType","type2":"wrappedType"}"""
)
}

Expand Down

0 comments on commit a33ef02

Please sign in to comment.