Skip to content

Commit

Permalink
Support other SerialFormats in LongOrStringSerializer (#937)
Browse files Browse the repository at this point in the history
Non-Json formats are supported by falling back to a String.
  • Loading branch information
lukellmann committed Apr 13, 2024
1 parent 14b98f4 commit 7fbcc24
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 10 deletions.
6 changes: 6 additions & 0 deletions common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ kotlin {
implementation(projects.kspAnnotations)
}
}
jvmTest {
dependencies {
implementation(libs.bson)
implementation(libs.kbson)
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.JsonEncoder
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.long

internal object LongOrStringSerializer : KSerializer<String> {
private val backingSerializer = JsonPrimitive.serializer()

/*
* Delegating serializers should not reuse descriptors:
* https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#delegating-serializers
*
* however `SerialDescriptor("...", backingSerializer.descriptor)` will throw since
* `JsonPrimitive.serializer().kind` is `PrimitiveKind.STRING` (`SerialDescriptor()` does not allow
* however `SerialDescriptor("...", JsonPrimitive.serializer().descriptor)` will throw since
* `JsonPrimitive.serializer().descriptor.kind` is `PrimitiveKind.STRING` (`SerialDescriptor()` does not allow
* `PrimitiveKind`)
* -> use `PrimitiveSerialDescriptor("...", PrimitiveKind.STRING)` instead
*/
Expand All @@ -26,12 +26,21 @@ internal object LongOrStringSerializer : KSerializer<String> {
)

override fun serialize(encoder: Encoder, value: String) {
val jsonPrimitive = value.toLongOrNull()?.let { JsonPrimitive(it) } ?: JsonPrimitive(value)
encoder.encodeSerializableValue(backingSerializer, jsonPrimitive)
if (encoder is JsonEncoder) {
val jsonPrimitive = value.toLongOrNull()?.let { JsonPrimitive(it) } ?: JsonPrimitive(value)
encoder.encodeJsonElement(jsonPrimitive)
} else {
// fall back to a String for non-Json formats
encoder.encodeString(value)
}
}

override fun deserialize(decoder: Decoder): String {
val jsonPrimitive = decoder.decodeSerializableValue(backingSerializer)
return if (jsonPrimitive.isString) jsonPrimitive.content else jsonPrimitive.long.toString()
}
override fun deserialize(decoder: Decoder): String =
if (decoder is JsonDecoder) {
val jsonPrimitive = decoder.decodeSerializableValue(JsonPrimitive.serializer())
if (jsonPrimitive.isString) jsonPrimitive.content else jsonPrimitive.long.toString()
} else {
// fall back to a String for non-Json formats
decoder.decodeString()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package dev.kord.common.serialization

import com.github.jershell.kbson.KBson
import dev.kord.common.entity.Permission.*
import dev.kord.common.entity.Permissions
import kotlinx.serialization.Serializable
import kotlin.random.Random
import kotlin.test.Test
import kotlin.test.assertEquals

class LongOrStringSerializerBsonTest {

@Serializable
private data class SomeObject(
@Serializable(with = LongOrStringSerializer::class) val someLong: String,
@Serializable(with = LongOrStringSerializer::class) val someString: String,
val somePermissions: Permissions,
)

private val someObject = SomeObject(
someLong = Random.nextLong().toString(),
someString = "some totally random string",
somePermissions = Permissions(DeafenMembers, ManageThreads, SendTTSMessages, ModerateMembers),
)

@Test
fun `test Bson serialization and deserialization with LongOrStringSerializer`() {
val kBson = KBson.default
assertEquals(
expected = someObject,
actual = kBson.load(SomeObject.serializer(), kBson.stringify(SomeObject.serializer(), someObject)),
)
assertEquals(
expected = someObject,
actual = kBson.load(SomeObject.serializer(), kBson.dump(SomeObject.serializer(), someObject)),
)
}
}
4 changes: 4 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ kotlinpoet = "1.16.0" # https://github.com/square/kotlinpoet
junit-jupiter = "5.10.2" # https://github.com/junit-team/junit5
junit-platform = "1.10.2"
mockk = "1.13.10" # https://github.com/mockk/mockk
bson = "5.0.1" # https://github.com/mongodb/mongo-java-driver
kbson = "0.5.0" # https://github.com/jershell/kbson

# plugins
dokka = "1.9.20" # https://github.com/Kotlin/dokka
Expand Down Expand Up @@ -81,6 +83,8 @@ junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", vers
junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher", version = "junit-platform" }
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" }
bson = { module = "org.mongodb:bson", version.ref = "bson" }
kbson = { module = "com.github.jershell:kbson", version.ref = "kbson" }

# actually plugins, not libraries, but used is 'buildSrc/build.gradle.kts' as implementation dependencies:
# https://docs.gradle.org/current/userguide/custom_plugins.html#applying_external_plugins_in_precompiled_script_plugins
Expand Down

0 comments on commit 7fbcc24

Please sign in to comment.