Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance KotlinSerializer with value codecs for widening primitive conversion. #1301

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
40 changes: 30 additions & 10 deletions bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/BsonDecoder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,18 @@ import org.bson.BsonInvalidOperationException
import org.bson.BsonReader
import org.bson.BsonType
import org.bson.BsonValue
import org.bson.codecs.BooleanCodec
import org.bson.codecs.BsonValueCodec
import org.bson.codecs.ByteCodec
import org.bson.codecs.CharacterCodec
import org.bson.codecs.DecoderContext
import org.bson.codecs.DoubleCodec
import org.bson.codecs.FloatCodec
import org.bson.codecs.IntegerCodec
import org.bson.codecs.LongCodec
import org.bson.codecs.ObjectIdCodec
import org.bson.codecs.ShortCodec
import org.bson.codecs.StringCodec
import org.bson.types.ObjectId

/**
Expand Down Expand Up @@ -67,6 +77,16 @@ internal open class DefaultBsonDecoder(
companion object {
val validKeyKinds = setOf(PrimitiveKind.STRING, PrimitiveKind.CHAR, SerialKind.ENUM)
val bsonValueCodec = BsonValueCodec()
val longCodec = LongCodec()
val shortCodec = ShortCodec()
val intCodec = IntegerCodec()
val doubleCodec = DoubleCodec()
val floatCodec = FloatCodec()
val byteCodec = ByteCodec()
val booleanCodec = BooleanCodec()
val chatCodec = CharacterCodec()
val stingCodec = StringCodec()
val objectIdCodec = ObjectIdCodec()
const val UNKNOWN_INDEX = -10
}

Expand Down Expand Up @@ -154,15 +174,15 @@ internal open class DefaultBsonDecoder(
}
}

override fun decodeByte(): Byte = decodeInt().toByte()
override fun decodeChar(): Char = decodeString().single()
override fun decodeFloat(): Float = decodeDouble().toFloat()
override fun decodeShort(): Short = decodeInt().toShort()
override fun decodeBoolean(): Boolean = readOrThrow({ reader.readBoolean() }, BsonType.BOOLEAN)
override fun decodeDouble(): Double = readOrThrow({ reader.readDouble() }, BsonType.DOUBLE)
override fun decodeInt(): Int = readOrThrow({ reader.readInt32() }, BsonType.INT32)
override fun decodeLong(): Long = readOrThrow({ reader.readInt64() }, BsonType.INT64)
override fun decodeString(): String = readOrThrow({ reader.readString() }, BsonType.STRING)
override fun decodeByte(): Byte = byteCodec.decode(reader, DecoderContext.builder().build())
override fun decodeChar(): Char = chatCodec.decode(reader, DecoderContext.builder().build())
override fun decodeFloat(): Float = floatCodec.decode(reader, DecoderContext.builder().build())
override fun decodeShort(): Short = shortCodec.decode(reader, DecoderContext.builder().build())
override fun decodeBoolean(): Boolean = booleanCodec.decode(reader, DecoderContext.builder().build())
override fun decodeDouble(): Double = doubleCodec.decode(reader, DecoderContext.builder().build())
override fun decodeInt(): Int = intCodec.decode(reader, DecoderContext.builder().build())
override fun decodeLong(): Long = longCodec.decode(reader, DecoderContext.builder().build())
override fun decodeString(): String = stingCodec.decode(reader, DecoderContext.builder().build())

override fun decodeNull(): Nothing? {
if (reader.state == AbstractBsonReader.State.VALUE) {
Expand All @@ -176,7 +196,7 @@ internal open class DefaultBsonDecoder(
return reader.state != AbstractBsonReader.State.END_OF_DOCUMENT && reader.currentBsonType != BsonType.NULL
}

override fun decodeObjectId(): ObjectId = readOrThrow({ reader.readObjectId() }, BsonType.OBJECT_ID)
override fun decodeObjectId(): ObjectId = objectIdCodec.decode(reader, DecoderContext.builder().build())
override fun decodeBsonValue(): BsonValue = bsonValueCodec.decode(reader, DecoderContext.builder().build())
override fun reader(): BsonReader = reader

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,17 @@ import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.plus
import kotlinx.serialization.modules.polymorphic
import kotlinx.serialization.modules.subclass
import org.bson.BsonBoolean
import org.bson.BsonDocument
import org.bson.BsonDocumentReader
import org.bson.BsonDocumentWriter
import org.bson.BsonDouble
import org.bson.BsonInt32
import org.bson.BsonInt64
import org.bson.BsonInvalidOperationException
import org.bson.BsonMaxKey
import org.bson.BsonMinKey
import org.bson.BsonString
import org.bson.BsonUndefined
import org.bson.codecs.DecoderContext
import org.bson.codecs.EncoderContext
Expand Down Expand Up @@ -87,6 +92,9 @@ import org.bson.codecs.kotlinx.samples.Key
import org.bson.codecs.kotlinx.samples.ValueClass
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource
import java.util.stream.Stream

@OptIn(ExperimentalSerializationApi::class)
class KotlinSerializerCodecTest {
Expand Down Expand Up @@ -129,15 +137,60 @@ class KotlinSerializerCodecTest {

private val allBsonTypesDocument = BsonDocument.parse(allBsonTypesJson)

@Test
fun testDataClassWithSimpleValues() {
val expected =
"""{"char": "c", "byte": 0, "short": 1, "int": 22, "long": {"$numberLong": "42"}, "float": 4.0,
| "double": 4.2, "boolean": true, "string": "String"}"""
.trimMargin()
val dataClass = DataClassWithSimpleValues('c', 0, 1, 22, 42L, 4.0f, 4.2, true, "String")

assertRoundTrips(expected, dataClass)
companion object {
@JvmStatic
fun testTypesCastingDataClassWithSimpleValues(): Stream<BsonDocument> {
return Stream.of(
BsonDocument().append("char", BsonString("c"))
.append("byte", BsonInt32(1))
.append("short", BsonInt32(2))
.append("int", BsonInt32(10))
.append("long", BsonInt32(10))
.append("float", BsonInt32(2))
.append("double", BsonInt32(3))
.append("boolean", BsonBoolean.TRUE)
.append("string", BsonString("String")),

BsonDocument().append("char", BsonString("c"))
.append("byte", BsonDouble(1.0))
.append("short", BsonDouble(2.0))
.append("int", BsonDouble(9.9999999999999992))
.append("long", BsonDouble(9.9999999999999992))
.append("float", BsonDouble(2.0))
.append("double", BsonDouble(3.0))
.append("boolean", BsonBoolean.TRUE)
.append("string", BsonString("String")),

BsonDocument().append("char", BsonString("c"))
.append("byte", BsonDouble(1.0))
.append("short", BsonDouble(2.0))
.append("int", BsonDouble(10.0))
.append("long", BsonDouble(10.0))
.append("float", BsonDouble(2.0))
.append("double", BsonDouble(3.0))
.append("boolean", BsonBoolean.TRUE)
.append("string", BsonString("String")),

BsonDocument().append("char", BsonString("c"))
.append("byte", BsonInt64(1))
.append("short", BsonInt64(2))
.append("int", BsonInt64(10))
.append("long", BsonInt64(10))
.append("float", BsonInt64(2))
.append("double", BsonInt64(3))
.append("boolean", BsonBoolean.TRUE)
.append("string", BsonString("String"))
)
}
}

@ParameterizedTest
@MethodSource("testTypesCastingDataClassWithSimpleValues")
fun testTypesCastingDataClassWithSimpleValues(data: BsonDocument) {
val expectedDataClass = DataClassWithSimpleValues('c', 1, 2, 10, 10L, 2.0f, 3.0, true, "String")

assertDecodesTo(data, expectedDataClass)
}

@Test
Expand Down