Overview
En esta guía, puede aprender sobre los códecs y las clases de soporte que manejan la codificación y decodificación de objetos Kotlin hacia y desde datos BSON. Codec la abstracción te permite mapear cualquier tipo de Kotlin a un tipo BSON correspondiente. Puedes utilizar esta funcionalidad para mapear objetos de dominio directamente hacia y desde BSON, en lugar de utilizar clases como Document o BsonDocument.
Puedes aprender cómo especificar la lógica personalizada de codificación y decodificación utilizando la abstracción Codec y ver implementaciones de ejemplo en las siguientes secciones:
Tip
Serialización de Kotlin
Puedes usar la serialización de Kotlin para gestionar tu codificación y decodificación de datos con las clases @Serializable en lugar de implementar codecs personalizados. Podrías elegir la serialización de Kotlin si ya estás familiarizado con la librería kotlinx.serialization o prefieres utilizar un enfoque idiomático de Kotlin. Para obtener más información, consulte la
Guía de serialización de datos.
Códec
La interfaz Codec contiene métodos abstractos para serializar y deserializar objetos Kotlin a datos BSON. Puedes definir una lógica de conversión personalizada en tu implementación de esta interfaz.
Para implementar la interfaz Codec, sobreescribe los métodos abstractos encode(), decode() y getEncoderClass().
Método encode()
El método encode() requiere los siguientes parámetros:
Parameter Type | Descripción |
|---|---|
| Una instancia de una clase que implementa |
| Los datos que tu implementación codifica. El tipo debe coincidir con la variable de tipo asignada a tu implementación. |
| Contiene metainformación acerca de los datos del objeto Kotlin que codifica en BSON, incluyendo si se debe almacenar el valor actual en una colección de MongoDB. |
El método encode() utiliza la instancia BsonWriter para enviar el valor codificado a MongoDB y no devuelve un valor.
Método decode()
El método decode() devuelve tu instancia de objeto Kotlin poblada con el valor de los datos BSON. Este método requiere los siguientes parámetros:
Parameter Type | Descripción |
|---|---|
| Una instancia de una clase que implementa |
| Contiene información sobre los datos BSON que decodifica en un objeto Kotlin. |
El método getEncoderClass() devuelve una instancia de clase de la clase Kotlin, ya que Kotlin no puede inferir el tipo debido a la eliminación de tipo.
Ejemplos
Esta sección contiene ejemplos de código que muestran cómo implementar una interfaz Codec personalizada.
El enum PowerStatus contiene los valores "ON" y "OFF" para representar los estados de un interruptor eléctrico:
enum class PowerStatus { ON, OFF }
La clase PowerStatusCodec implementa la interfaz Codec para convertir los valores enum de Kotlin en los valores booleanos BSON correspondientes. El método encode() convierte un valor PowerStatus en un booleano BSON y el método decode() realiza la conversión en sentido inverso.
class PowerStatusCodec : Codec<PowerStatus> { override fun encode( writer: BsonWriter, value: PowerStatus, encoderContext: EncoderContext ) = writer.writeBoolean(value == PowerStatus.ON) override fun decode( reader: BsonReader, decoderContext: DecoderContext) : PowerStatus { return when (reader.readBoolean()) { true -> PowerStatus.ON false -> PowerStatus.OFF } } override fun getEncoderClass(): Class<PowerStatus> = PowerStatus::class.java }
Puedes agregar una instancia de PowerStatusCodec a tu CodecRegistry. Consulta la sección CodecRegistry de esta página para aprender cómo incluir tu Codec en tu registro.
Para obtener más información sobre las clases e interfaces mencionadas en esta sección, consulte la siguiente documentación de la API:
CodecRegistry
Un CodecRegistry es una colección inmutable de instancias Codec que codifican y decodifican clases de Kotlin. Se puede utilizar cualquier método de fábrica estático de la clase CodecRegistries para construir un CodecRegistry a partir de las instancias Codec contenidas en los tipos asociados:
fromCodecs(): Crea un registro a partir de instanciasCodecfromProviders(): Crea un registro a partir de instanciasCodecProviderfromRegistries(): Crea un registro a partir de instanciasCodecRegistry
El siguiente código muestra cómo construir un CodecRegistry utilizando el método fromCodecs():
val codecRegistry = CodecRegistries .fromCodecs(IntegerCodec(), PowerStatusCodec())
El ejemplo anterior asigna a CodecRegistry las siguientes implementaciones Codec:
IntegerCodec:Codecque convierteIntegers. Forma parte del paquete BSON.PowerStatusCodec:MuestraCodecde la sección anterior que convierte valores de enumeración de Kotlin en booleanos BSON.
Puede recuperar las instancias de Codec de la instancia de CodecRegistry utilizando el siguiente código:
val powerStatusCodec = codecRegistry.get(PowerStatus::class.java) val integerCodec = codecRegistry.get(Integer::class.java)
Si intenta recuperar una instancia Codec para una clase que no está registrada, el método codecRegistry.get() genera una excepción CodecConfigurationException.
Para obtener más información sobre las clases e interfaces de esta sección, consulte la siguiente documentación de API:
CodecProvider
Un CodecProvider es una interfaz que contiene métodos abstractos para crear instancias de Codec y asignarlas a instancias de CodecRegistry. De manera similar a la interfaz CodecRegistry, la biblioteca BSON utiliza las instancias Codec recuperadas por el método CodecProvider.get() para convertir entre los tipos de datos de Kotlin y BSON.
Sin embargo, en los casos en los que agregues una clase que contenga campos que requieran objetos Codec correspondientes, asegúrate de instanciar los objetos Codec para los campos de la clase antes de instanciar el Codec para toda la clase. Puedes usar el parámetro CodecRegistry en el método CodecProvider.get() para pasar cualquiera de las instancias Codec de las que depende el Codec.
Para ver un ejemplo ejecutable que demuestre operaciones de lectura y escritura utilizando Codec clases, consulte la sección Ejemplo de códec personalizado de esta guía.
Registro de códecs por defecto
El registro de códecs por defecto es un conjunto de clases de CodecProvider que especifican la conversión entre objetos de uso común de Kotlin y tipos de MongoDB. El driver utiliza automáticamente el registro de codec por defecto a menos que se especifique uno diferente.
Para sobrescribir el comportamiento de una o más clases Codec, pero mantener el comportamiento del registro de códec por defecto para las otras clases, puedes especificar los registros en orden de precedencia. Por ejemplo, suponga que desea reemplazar el comportamiento del proveedor predeterminado de un Codec para tipos de enumeración con su propio MyEnumCodec personalizado. Debes añadirlo a la lista de registro en una posición antes del registro de códecs por defecto, como se muestra en el siguiente ejemplo:
val newRegistry = CodecRegistries.fromRegistries( CodecRegistries.fromCodecs(MyEnumCodec()), MongoClientSettings.getCodecRegistry() )
Para obtener más información sobre las clases e interfaces de esta sección, consulte la siguiente documentación de API:
Mapa de clases de tipos Bson
La clase BsonTypeClassMap contiene una asignación recomendada entre BSON y los tipos de Kotlin. Puede utilizar esta clase en su Codec o CodecProvider personalizado para ayudarle a gestionar a qué tipos de Kotlin desea decodificar sus BSON types. También contiene clases contenedoras que implementan Iterable o Map como la clase Document.
Puede agregar o modificar la asignación BsonTypeClassMap por defecto pasando un Map que contenga nuevas entradas o reemplazos.
El siguiente código muestra cómo recuperar el tipo de clase de Kotlin que corresponde al tipo de arreglo BSON en la instancia predeterminada de BsonTypeClassMap:
val bsonTypeClassMap = BsonTypeClassMap() val clazz = bsonTypeClassMap[BsonType.ARRAY] println("Kotlin class name: " + clazz.name)
Kotlin class name: java.util.List
Puedes modificar estas asignaciones en tu instancia especificando reemplazos en el constructor BsonTypeClassMap. El siguiente snippet muestra cómo se puede reemplazar la asignación para el tipo de arreglo BSON en su instancia de BsonTypeClassMap con la clase Set:
val replacements = mutableMapOf<BsonType, Class<*>>(BsonType.ARRAY to MutableSet::class.java) val bsonTypeClassMap = BsonTypeClassMap(replacements) val clazz = bsonTypeClassMap[BsonType.ARRAY] println("Class name: " + clazz.name)
Kotlin class name: java.util.Set
Para obtener una lista completa de las asignaciones predeterminadas, consulta la BsonTypeClassMap documentación de API.
Ejemplo de Codec Personalizado
Esta sección muestra cómo implementar las interfaces Codec y CodecProvider para definir la lógica de codificación y decodificación de una clase Kotlin personalizada. También muestra cómo especificar y usar sus implementaciones personalizadas para realizar operaciones de lectura y escritura.
El siguiente código define la clase de datos de muestra Monolight:
data class Monolight( var powerStatus: PowerStatus = PowerStatus.OFF, var colorTemperature: Int? = null ) { override fun toString(): String = "Monolight { powerStatus: $powerStatus, colorTemperature: $colorTemperature }" }
Esta clase contiene los siguientes campos que requieren cada uno un Codec correspondiente para gestionar la codificación y decodificación:
powerStatus: Describe si la luz del dispositivo está"ON"o"OFF". Para este campo, use la PowerStatusCodec que convierte los valores de la enumeraciónPowerStatusen booleanos BSON.colorTemperatureDescribe el color de la luz del dispositivo en kelvins como un valorInt. Para este campo, utilice el valorIntegerCodecproporcionado en la biblioteca BSON.
El siguiente código muestra cómo implementar un Codec para la clase Monolight. El constructor espera una instancia de CodecRegistry, de la cual recupera las instancias Codec necesarias para codificar y decodificar los campos de la clase:
class MonolightCodec(registry: CodecRegistry) : Codec<Monolight> { private val powerStatusCodec: Codec<PowerStatus> private val integerCodec: Codec<Int> init { powerStatusCodec = registry[PowerStatus::class.java] integerCodec = IntegerCodec() } override fun encode(writer: BsonWriter, value: Monolight, encoderContext: EncoderContext) { writer.writeStartDocument() writer.writeName("powerStatus") powerStatusCodec.encode(writer, value.powerStatus, encoderContext) writer.writeName("colorTemperature") integerCodec.encode(writer, value.colorTemperature, encoderContext) writer.writeEndDocument() } override fun decode(reader: BsonReader, decoderContext: DecoderContext): Monolight { val monolight = Monolight() reader.readStartDocument() while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) { when (reader.readName()) { "powerStatus" -> monolight.powerStatus = powerStatusCodec.decode(reader, decoderContext) "colorTemperature" -> monolight.colorTemperature = integerCodec.decode(reader, decoderContext) "_id" -> reader.readObjectId() } } reader.readEndDocument() return monolight } override fun getEncoderClass(): Class<Monolight> = Monolight::class.java }
Para garantizar que las instancias Codec de los campos estén disponibles para la clase Monolight, implemente un CodecProvider personalizado como se muestra en el siguiente ejemplo de código:
class MonolightCodecProvider : CodecProvider { override fun <T> get(clazz: Class<T>, registry: CodecRegistry): Codec<T>? { return if (clazz == Monolight::class.java) { MonolightCodec(registry) as Codec<T> } else null // Return null when not a provider for the requested class } }
Después de definir la lógica de conversión, puedes realizar las siguientes acciones:
Almacene instancias de
Monolighten MongoDBRecuperar documentos de MongoDB como instancias de
Monolight
El siguiente código asigna el MonolightCodecProvider a la instancia MongoCollection pasándolo al método withCodecRegistry(). La clase de ejemplo también inserta y recupera datos utilizando la clase Monolight:
val mongoClient = MongoClient.create(uri) val codecRegistry = CodecRegistries.fromRegistries( CodecRegistries.fromCodecs(IntegerCodec(), PowerStatusCodec()), CodecRegistries.fromProviders(MonolightCodecProvider()), MongoClientSettings.getDefaultCodecRegistry() ) val database = mongoClient.getDatabase("codec_test") val collection = database.getCollection<Monolight>("monolights") .withCodecRegistry(codecRegistry) // Insert instances of Monolight val monolights = listOf( Monolight(PowerStatus.ON, 5200), Monolight(PowerStatus.OFF, 3000) ) collection.insertMany(monolights) // Retrieve instances of Monolight val results = collection.find() results.forEach { l -> println(l) }
Monolight { powerStatus: ON, colorTemperature: 5200 } Monolight { powerStatus: OFF, colorTemperature: 3000 }
Para obtener más información sobre los métodos y clases mencionados en esta sección, consulte la siguiente documentación de API: