diff --git a/bson-official/src/jvmMain/kotlin/BsonArrayWriter.jvm.kt b/bson-official/src/jvmMain/kotlin/BsonArrayWriter.jvm.kt --- a/bson-official/src/jvmMain/kotlin/BsonArrayWriter.jvm.kt +++ b/bson-official/src/jvmMain/kotlin/BsonArrayWriter.jvm.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, OpenSavvy and contributors. + * Copyright (c) 2024-2026, OpenSavvy and contributors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,28 +22,12 @@ import opensavvy.ktmongo.bson.official.types.toOfficial import opensavvy.ktmongo.bson.types.Timestamp import opensavvy.ktmongo.dsl.LowLevelApi +import org.bson.* import org.bson.BsonArray -import org.bson.BsonBinary -import org.bson.BsonBoolean -import org.bson.BsonDateTime -import org.bson.BsonDbPointer -import org.bson.BsonDecimal128 import org.bson.BsonDocument -import org.bson.BsonDocumentWriter -import org.bson.BsonDouble -import org.bson.BsonInt32 -import org.bson.BsonInt64 -import org.bson.BsonJavaScript -import org.bson.BsonMaxKey -import org.bson.BsonMinKey -import org.bson.BsonNull -import org.bson.BsonObjectId -import org.bson.BsonRegularExpression -import org.bson.BsonString -import org.bson.BsonSymbol -import org.bson.BsonUndefined import org.bson.types.Decimal128 import org.bson.types.ObjectId +import kotlin.reflect.KType @LowLevelApi internal class JavaBsonArrayWriter( @@ -150,11 +134,11 @@ } @LowLevelApi - override fun writeObjectSafe(obj: T) { + override fun writeSafe(obj: T, type: KType) { val document = BsonDocument() BsonDocumentWriter(document).use { writer -> - JavaBsonDocumentWriter(factory, writer).writeObjectSafe(obj) + JavaBsonDocumentWriter(factory, writer).writeSafe(obj, type) } array.add(document) diff --git a/bson-official/src/jvmMain/kotlin/BsonDocumentWriter.jvm.kt b/bson-official/src/jvmMain/kotlin/BsonDocumentWriter.jvm.kt --- a/bson-official/src/jvmMain/kotlin/BsonDocumentWriter.jvm.kt +++ b/bson-official/src/jvmMain/kotlin/BsonDocumentWriter.jvm.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, OpenSavvy and contributors. + * Copyright (c) 2024-2026, OpenSavvy and contributors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,10 +26,10 @@ import org.bson.BsonDbPointer import org.bson.BsonRegularExpression import org.bson.BsonWriter -import org.bson.codecs.Encoder import org.bson.codecs.EncoderContext import org.bson.types.Decimal128 import org.bson.types.ObjectId +import kotlin.reflect.KType @OptIn(LowLevelApi::class) internal class JavaBsonDocumentWriter( @@ -112,9 +112,9 @@ writer.writeEndArray() } - override fun writeObjectSafe(name: String, obj: T) { + override fun writeSafe(name: String, obj: T, type: KType) { writer.writeName(name) - writeObjectSafe(obj) + writeSafe(obj, type) } @Deprecated(DEPRECATED_IN_BSON_SPEC) @@ -213,12 +213,12 @@ writer.writeEndArray() } - override fun writeObjectSafe(obj: T) { + override fun writeSafe(obj: T, type: KType) { if (obj == null) { writer.writeNull() } else { @Suppress("UNCHECKED_CAST", "UNNECESSARY_NOT_NULL_ASSERTION") - val codec = factory.codecRegistry.get(obj!!::class.java) as Encoder + val codec = factory.findCodecForType(type) codec.encode( writer, obj, diff --git a/bson/src/commonMain/kotlin/BsonWriter.kt b/bson/src/commonMain/kotlin/BsonWriter.kt --- a/bson/src/commonMain/kotlin/BsonWriter.kt +++ b/bson/src/commonMain/kotlin/BsonWriter.kt @@ -22,6 +22,8 @@ import opensavvy.ktmongo.dsl.DangerousMongoApi import opensavvy.ktmongo.dsl.LowLevelApi import kotlin.experimental.and +import kotlin.reflect.KType +import kotlin.reflect.typeOf import kotlin.time.Instant /** @@ -110,8 +112,90 @@ * Writes an arbitrary [obj] into a BSON document. * * All nested values are escaped as necessary such that the result is a completely inert BSON document. + * + * ### Serialization configuration + * + * This method uses the serialization methods configured in the [BsonFactory] that created this instance. + * + * For example, if you use the official Java or Kotlin MongoDB drivers, + * this method will use your configured `CodecRegistry`. + * + * ### Example + * + * ```kotlin + * data class User( + * val _id: ObjectId, + * val profile: Profile, + * ) + * + * data class Profile( + * val name: String, + * val age: Int?, + * ) + * + * val factory: BsonFactory = … + * + * val bson = factory.buildDocument { + * write("user") { + * writeSafe("user", User(ObjectId("69c93e17b96e83b72d11b734"), Profile("Bob", 30))) + * } + * } + * + * val user = bson.decode() + * + * println(user._id) // ObjectId(69c93e17b96e83b72d11b734) + * println(user.profile.name) // Bob + * println(user.profile.age) // 30 + * ``` */ - @LowLevelApi fun writeObjectSafe(obj: T) + @LowLevelApi + fun writeSafe(obj: T, type: KType) + + /** + * Writes an arbitrary [obj] into a BSON document. + * + * All nested values are escaped as necessary such that the result is a completely inert BSON document. + * + * ### Serialization configuration + * + * This method uses the serialization methods configured in the [BsonFactory] that created this instance. + * + * For example, if you use the official Java or Kotlin MongoDB drivers, + * this method will use your configured `CodecRegistry`. + * + * ### Example + * + * ```kotlin + * data class User( + * val _id: ObjectId, + * val profile: Profile, + * ) + * + * data class Profile( + * val name: String, + * val age: Int?, + * ) + * + * val factory: BsonFactory = … + * + * val bson = factory.buildDocument { + * write("user") { + * writeSafe("user", User(ObjectId("69c93e17b96e83b72d11b734"), Profile("Bob", 30))) + * } + * } + * + * val user = bson.decode() + * + * println(user._id) // ObjectId(69c93e17b96e83b72d11b734) + * println(user.profile.name) // Bob + * println(user.profile.age) // 30 + * ``` + */ + @Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION") + @LowLevelApi + final inline fun writeSafe(obj: T) { + this.writeSafe(obj, typeOf()) + } /** * Writes the arbitrary [obj] into this writer. @@ -119,7 +203,7 @@ * Note that the object will be written as-is, with no safety checks whatsoever. * Only use this method if you are absolutely sure attackers cannot control the contents of [obj]. * - * If in doubt, prefer using [writeObjectSafe]. + * If in doubt, prefer using [writeSafe]. */ @LowLevelApi @DangerousMongoApi @@ -247,8 +331,86 @@ * Writes an arbitrary [obj] into a BSON document. * * All nested values are escaped as necessary such that the result is a completely inert BSON document. + * + * ### Serialization configuration + * + * This method uses the serialization methods configured in the [BsonFactory] that created this instance. + * + * For example, if you use the official Java or Kotlin MongoDB drivers, + * this method will use your configured `CodecRegistry`. + * + * ### Example + * + * ```kotlin + * data class User( + * val _id: ObjectId, + * val profile: Profile, + * ) + * + * data class Profile( + * val name: String, + * val age: Int?, + * ) + * + * val factory: BsonFactory = … + * + * val bson = factory.buildDocument { + * writeSafe("user", User(ObjectId("69c93e17b96e83b72d11b734"), Profile("Bob", 30))) + * } + * + * val user = bson.decode() + * + * println(user._id) // ObjectId(69c93e17b96e83b72d11b734) + * println(user.profile.name) // Bob + * println(user.profile.age) // 30 + * ``` */ - @LowLevelApi fun writeObjectSafe(name: String, obj: T) + @LowLevelApi + fun writeSafe(name: String, obj: T, type: KType) + + /** + * Writes an arbitrary [obj] into a BSON document. + * + * All nested values are escaped as necessary such that the result is a completely inert BSON document. + * + * ### Serialization configuration + * + * This method uses the serialization methods configured in the [BsonFactory] that created this instance. + * + * For example, if you use the official Java or Kotlin MongoDB drivers, + * this method will use your configured `CodecRegistry`. + * + * ### Example + * + * ```kotlin + * data class User( + * val _id: ObjectId, + * val profile: Profile, + * ) + * + * data class Profile( + * val name: String, + * val age: Int?, + * ) + * + * val factory: BsonFactory = … + * + * val bson = factory.buildDocument { + * writeSafe("user", User(ObjectId("69c93e17b96e83b72d11b734"), Profile("Bob", 30))) + * } + * + * val user = bson.decode() + * + * println(user._id) // ObjectId(69c93e17b96e83b72d11b734) + * println(user.profile.name) // Bob + * println(user.profile.age) // 30 + * ``` + */ + @Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION") + @LowLevelApi + final inline fun writeSafe(name: String, obj: T) { + this.writeSafe(name, obj, typeOf()) + } // No 'pipe' overload because it would encourage people to use it. // If you really must use 'pipe', use 'write("name") { pipe(…) }' diff --git a/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformArrayFieldWriter.kt b/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformArrayFieldWriter.kt --- a/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformArrayFieldWriter.kt +++ b/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformArrayFieldWriter.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, OpenSavvy and contributors. + * Copyright (c) 2025-2026, OpenSavvy and contributors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import opensavvy.ktmongo.bson.types.Timestamp import opensavvy.ktmongo.dsl.DangerousMongoApi import opensavvy.ktmongo.dsl.LowLevelApi +import kotlin.reflect.KType @LowLevelApi internal class MultiplatformArrayFieldWriter( @@ -157,7 +158,7 @@ writer.openArray(nextIndex()) @LowLevelApi - override fun writeObjectSafe(obj: T) { - writer.writeObjectSafe(nextIndex(), obj) + override fun writeSafe(obj: T, type: KType) { + writer.writeSafe(nextIndex(), obj, type) } } diff --git a/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformDocumentFieldWriter.kt b/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformDocumentFieldWriter.kt --- a/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformDocumentFieldWriter.kt +++ b/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformDocumentFieldWriter.kt @@ -28,6 +28,7 @@ import opensavvy.ktmongo.bson.types.Timestamp import opensavvy.ktmongo.dsl.DangerousMongoApi import opensavvy.ktmongo.dsl.LowLevelApi +import kotlin.reflect.KType @LowLevelApi internal class MultiplatformDocumentFieldWriter( @@ -282,7 +283,7 @@ } @LowLevelApi - override fun writeObjectSafe(name: String, obj: T) { + override fun writeSafe(name: String, obj: T, type: KType) { TODO() } diff --git a/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformSingleFieldWriter.kt b/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformSingleFieldWriter.kt --- a/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformSingleFieldWriter.kt +++ b/bson-multiplatform/src/commonMain/kotlin/impl/write/MultiplatformSingleFieldWriter.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, OpenSavvy and contributors. + * Copyright (c) 2025-2026, OpenSavvy and contributors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import opensavvy.ktmongo.bson.types.Timestamp import opensavvy.ktmongo.dsl.DangerousMongoApi import opensavvy.ktmongo.dsl.LowLevelApi +import kotlin.reflect.KType @LowLevelApi internal class MultiplatformSingleFieldWriter( @@ -140,8 +141,8 @@ } @LowLevelApi - override fun writeObjectSafe(obj: T) { - writer.writeObjectSafe(name, obj) + override fun writeSafe(obj: T, type: KType) { + writer.writeSafe(name, obj, type) } override fun complete() {