Docs Menu
Docs Home
/ /

Codificar datos con códecs de tipo

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 en el controlador Kotlin de MongoDB. Codec La abstracción permite mapear cualquier tipo de Kotlin a su tipo BSON correspondiente. Esto permite mapear los objetos de dominio directamente desde y hacia BSON, en lugar de usar clases de datos o un objeto intermedio basado en mapeo, como Document o BsonDocument.

Puede aprender a especificar la lógica de codificación y decodificación personalizada utilizando la abstracción Codec y ver implementaciones de ejemplo en las siguientes secciones:

  • Códec

  • Registro de códecs

  • Proveedor de códecs

  • Ejemplo de códec personalizado

La interfaz Codec contiene métodos abstractos para serializar y deserializar objetos Kotlin a datos BSON. Puede definir la lógica de conversión entre BSON y su objeto Kotlin en la implementación de esta interfaz.

Para implementar la interfaz Codec, anule los métodos abstractos encode(), decode() y getEncoderClass().

El método encode() requiere los siguientes parámetros:

Parameter Type
Descripción

writer

Una instancia de una clase que implementa BsonWriter, un tipo de interfaz que expone métodos para escribir un documento BSON. Por ejemplo, la implementación BsonBinaryWriter escribe en un flujo binario de datos. Utilice esta instancia para escribir su valor BSON con el método de escritura adecuado.

value

Los datos que codifica su implementación. El tipo debe coincidir con la variable de tipo asignada a su implementación.

encoderContext

Contiene metainformación sobre los datos del objeto Kotlin que codifica en BSON, incluido si se debe almacenar el valor actual en una colección MongoDB.

Este método utiliza la instancia BsonWriter para enviar el valor codificado a MongoDB y no devuelve un valor.

El método decode() devuelve la instancia del objeto Kotlin rellenada con el valor de los datos BSON. Este método requiere los siguientes parámetros:

Parameter Type
Descripción

bsonReader

Una instancia de una clase que implementa BsonReader, un tipo de interfaz que expone métodos para leer un documento BSON. Por ejemplo, la implementación BsonBinaryReader lee de un flujo binario de datos.

decoderContext

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.

Vea los siguientes ejemplos de código que muestran cómo puede implementar un Codec personalizado.

La enumeración 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 Codec para convertir los valores enum de Kotlin en los valores booleanos BSON correspondientes. El método encode() convierte un valor PowerStatus en un valor 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 la cual contiene una asignación entre tu Codec y el tipo de objeto Kotlin al que se aplica. Continúa en la sección CodecRegistry de esta página para ver cómo puedes incluir Codec tu.

Para obtener más información sobre las clases e interfaces en esta sección, consulte la siguiente documentación de API:

Un CodecRegistry es una colección inmutable de instancias Codec que codifican y decodifican las clases Kotlin que especifican. Puede usar cualquiera de los siguientes métodos de fábrica estáticos de clase CodecRegistries para construir un CodecRegistry a partir de las instancias Codec contenidas en los tipos asociados:

  • fromCodecs()

  • fromProviders()

  • fromRegistries()

El siguiente fragmento de código muestra cómo construir un CodecRegistry utilizando el método fromCodecs():

val codecRegistry = CodecRegistries.fromCodecs(IntegerCodec(), PowerStatusCodec())

En el ejemplo anterior, asignamos a CodecRegistry las siguientes implementaciones de Codec:

  • IntegerCodec, un Codec que convierte Integers y es parte del paquete BSON.

  • PowerStatusCodec, nuestra muestra Codec que convierte valores de enumeración Kotlin en booleanos BSON.

Puedes recuperar las instancias de Codec de la instancia CodecRegistry del ejemplo anterior 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 get() genera una excepción CodecConfigurationException.

Para obtener más información sobre las clases e interfaces en esta sección, consulte la siguiente documentación de API:

Una CodecProvider es una interfaz que contiene métodos abstractos que crean instancias Codec y las asignan a una instancia CodecRegistry. Al igual que la CodecRegistry, la biblioteca BSON utiliza las instancias Codec recuperadas por el método get() para convertir entre los tipos de datos de Kotlin y BSON.

Sin embargo, si agrega una clase que contiene campos que requieren los objetos Codec correspondientes, debe asegurarse de instanciar los objetos Codec para los campos de la clase antes de instanciar los objetos Codec. Puede usar el parámetro CodecRegistry en el método get() para pasar cualquiera de las instancias Codec de las que depende el método Codec.

El siguiente ejemplo de código muestra cómo se puede implementar CodecProvider para pasar a MonolightCodec cualquier instancia de Codec que necesite en una instancia de CodecRegistry como la PowerStatusCodec de nuestro ejemplo anterior:

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 ver un ejemplo ejecutable que demuestre operaciones de lectura y escritura utilizando estas Codec clases, consulte la sección Ejemplo de códec personalizado de esta guía.

El registro de códecs predeterminado es un conjunto de clases CodecProvider que especifican la conversión entre los tipos de Kotlin y MongoDB más comunes. El controlador utiliza automáticamente el registro de códecs predeterminado a menos que se especifique uno diferente.

Si necesita anular el comportamiento de una o más clases Codec, pero conservar el comportamiento del registro de códecs predeterminado para las demás clases, puede especificar todos los registros en orden de precedencia. Por ejemplo, supongamos que desea anular el comportamiento predeterminado del proveedor de un Codec para tipos de enumeración con su MyEnumCodec personalizado; debe añadirlo a la lista de registros antes que al registro de códecs predeterminado, como se muestra en el siguiente ejemplo:

val newRegistry = CodecRegistries.fromRegistries(
CodecRegistries.fromCodecs(MyEnumCodec()),
MongoClientSettings.getDefaultCodecRegistry()
)

Para obtener más información sobre las clases e interfaces de esta sección, consulte las siguientes secciones de la documentación de API:

La clase BsonTypeClassMap contiene una asignación recomendada entre tipos BSON y Kotlin. Puede usar esta clase en sus Codec o CodecProvider personalizados para gestionar qué tipos Kotlin decodificar sus tipos BSON en 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 fragmento de código muestra cómo puede recuperar el tipo de clase Kotlin que corresponde al tipo BSON en la instancia predeterminada BsonTypeClassMap:

val bsonTypeClassMap = BsonTypeClassMap()
val clazz = bsonTypeClassMap[BsonType.ARRAY]
println("Class name: " + clazz.name)
Java type: java.util.List

Puede modificar estas asignaciones en su instancia especificando reemplazos en el constructor BsonTypeClassMap. El siguiente fragmento de código muestra cómo reemplazar la asignación de ARRAY en su instancia 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)
Java type: java.util.Set

Para obtener una lista completa de las asignaciones predeterminadas, consulte la documentación de la API BsonTypeClassMap.

Para ver un ejemplo de cómo la clase Document usa BsonTypeClassMap, consulte el código fuente del controlador para las siguientes clases:

En esta sección, mostramos cómo implementar Codec y CodecProvider para definir la lógica de codificación y decodificación de una clase Kotlin personalizada. También mostramos cómo especificar y usar sus implementaciones personalizadas para realizar operaciones de inserción y recuperación.

Tip

Serialización de Kotlin

Como alternativa a la implementación de códecs personalizados, puede usar la serialización de Kotlin para gestionar la codificación y decodificación de datos con clases @Serializable. Puede optar por la serialización de Kotlin si ya está familiarizado con el framework o si prefiere usar un enfoque idiomático de Kotlin. Consulte Documentación de serialización de Kotlin para obtener más información.

El siguiente fragmento de código muestra nuestra clase personalizada de ejemplo llamada Monolight y sus campos que queremos almacenar y recuperar de una colección MongoDB:

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, a cada uno de los cuales debemos asignarle un Codec:

  • powerStatus describe si la luz está "encendida" o "apagada", para lo cual utilizamos PowerStatusCodec que convierte valores de enumeración específicos en booleanos BSON.

  • colorTemperature describe el color de la luz y contiene un valor Int para el cual usamos el IntegerCodec incluido en la biblioteca BSON.

El siguiente ejemplo de código muestra cómo implementar un Codec para la clase Monolight. Tenga en cuenta que el constructor espera una instancia de CodecRegistry, de la cual recupera las instancias Codec necesarias para codificar y decodificar sus campos:

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 Monolight, implementamos un CodecProvider personalizado que se muestra en el siguiente ejemplo de código:

class MonolightCodecProvider : CodecProvider {
@Suppress("UNCHECKED_CAST")
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, podemos realizar lo siguiente:

  • Almacena datos de instancias de Monolight en MongoDB

  • Recuperar datos de MongoDB en instancias de Monolight

La siguiente clase de ejemplo contiene código que asigna el valor MonolightCodecProvider a la instancia MongoCollection pasándolo al método withCodecRegistry(). La clase de ejemplo también inserta y recupera datos mediante la clase Monolight y los códecs asociados:

fun main() = runBlocking {
val mongoClient = MongoClient.create("<connection string uri>")
val codecRegistry = CodecRegistries.fromRegistries(
CodecRegistries.fromCodecs(IntegerCodec(), PowerStatusCodec()),
CodecRegistries.fromProviders(MonolightCodecProvider()),
MongoClientSettings.getDefaultCodecRegistry()
)
val database = mongoClient.getDatabase("codecs_example_products")
val collection = database.getCollection<Monolight>("monolights")
.withCodecRegistry(codecRegistry)
// Construct and insert an instance of Monolight
val myMonolight = Monolight(PowerStatus.ON, 5200)
collection.insertOne(myMonolight)
// Retrieve one or more instances of Monolight
val lights = collection.find().toList()
println(lights)
}
[Monolight [powerStatus=ON, colorTemperature=5200]]

Para obtener más información sobre los métodos y clases mencionados en esta sección, consulte la siguiente documentación de API:

Volver

Serialización de Kotlin

En esta página