Menu Docs
Página inicial do Docs
/ / /
Kotlin Coroutine
/ /

Codecs

Nesta página

  • Visão geral
  • Codec
  • CodecRegistry
  • CodecProvider
  • Registro Codec padrão
  • BsonTypeClassMap
  • Exemplo de codec personalizado

Neste guia, você pode aprender sobre Codecs e as classes de suporte que lidam com a codificação e decodificação de objetos Kotlin de e para dados BSON no driver Kotlin do MongoDB. A abstração Codec permite mapear qualquer tipo de Kotlin para um tipo de BSON correspondente. Você pode usar isso para mapear seus objetos de domínio diretamente de e para o BSON, em vez de usar classes de dados ou um objeto intermediário baseado em mapa, como Document ou BsonDocument.

Você pode aprender a especificar a lógica de codificação e decodificação personalizada usando a Codec abstração e exibir implementações de exemplo nas seções a seguir:

  • Codec

  • CodecRegistry

  • CodecProvider

  • Exemplo de codec personalizado

A interface do Codec contém métodos abstratos para serializar e desserializar objetos Kotlin para dados BSON. Você pode definir sua lógica de conversão entre o BSON e seu objeto Kotlin em sua implementação desta interface.

Para implementar a interface do Codec, substitua os métodos abstratos encode(), decode() e getEncoderClass().

O método encode() exige os seguintes parâmetros:

Parameter Type
Descrição
writer
Uma instância de uma classe que implementa o BsonWriter, um tipo de interface que expõe métodos para escrever um documento BSON. Por exemplo, a implementação do BsonBinaryWriter grava em um fluxo binário de dados. Use esta instância para escrever seu valor BSON usando o método de escrita apropriado.
value
Os dados que sua implementação codifica. O tipo deve corresponder à variável de tipo atribuída à sua implementação.
encoderContext
Contém informação meta sobre os dados de objeto Kotlin que ele codifica para BSON, incluindo se deve armazenar o valor atual em uma coleção MongoDB.

Este método utiliza a instância BsonWriter para enviar o valor codificado para o MongoDB e não retorna um valor.

O método decode() retorna sua instância de objeto Kotlin preenchida com o valor dos dados BSON. Este método requer os seguintes parâmetros:

Parameter Type
Descrição
bsonReader
Uma instância de uma classe que implementa o BsonReader, um tipo de interface que expõe métodos para ler um documento BSON. Por exemplo, a implementação do BsonBinaryReader lê a partir de um fluxo binário de dados.
decoderContext
Contém informações sobre os dados BSON que decodifica para um objeto Kotlin.

O método getEncoderClass() retorna uma instância de classe Kotlin uma vez que Kotlin não pode inferir o tipo devido ao apagamento do tipo.

Consulte os seguintes exemplos de código que mostram como você pode implementar um Codec personalizado.

O PowerStatus enum contém os valores "ON" e "OFF" para representar os estados de um interruptor elétrico.

enum class PowerStatus {
ON,
OFF
}

A classe PowerStatusCodec implementa Codec para converter os valores Kotlin enum em valores booleanos BSON correspondentes. O método encode() converte um PowerStatus em um booleano BSON e o método decode() executa a conversão na direção oposta.

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
}

Você pode adicionar uma instância do PowerStatusCodec ao seu CodecRegistry que contém um mapeamento entre seu Codec e o tipo de objeto Kotlin ao qual ele se aplica. Continue para a seção CodecRegistry desta página para ver como você pode incluir seu Codec.

Para obter mais informações sobre as classes e interfaces nesta seção, consulte a seguinte Documentação da API:

CodecRegistry Um coleção é uma coleção imutável de Codec instâncias que codificam e decodificam as classes Kotlin especificadas. Você pode usar qualquer um dos seguintes métodos de fábrica estática de classe CodecRegistries para construir uma CodecRegistry a partir das instâncias Codec contidas nos tipos associados:

  • fromCodecs()

  • fromProviders()

  • fromRegistries()

O seguinte trecho de código mostra como construir um CodecRegistry utilizando o método fromCodecs():

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

No exemplo anterior, atribuímos a CodecRegistry as seguintes implementações de Codec:

  • IntegerCodec, um Codec que converte Integers e faz parte do pacote BSON.

  • PowerStatusCodec, nossa amostra Codec que converte valores enum Kotlin em booleanos BSON.

Você pode recuperar as instâncias Codec da instância CodecRegistry do exemplo anterior usando o código a seguir:

val powerStatusCodec = codecRegistry.get(PowerStatus::class.java)
val integerCodec = codecRegistry.get(Integer::class.java)

Se você tentar recuperar uma instância do Codec para uma classe que não está registrada, o método do get() lançará uma exceção do CodecConfigurationException.

Para obter mais informações sobre as classes e interfaces nesta seção, consulte a seguinte Documentação da API:

Um CodecProvider é uma interface que contém métodos abstratos que criam instâncias do Codec e atribuem a uma instância do CodecRegistry . Semelhante ao CodecRegistry, a biblioteca BSON usa as instâncias do Codec recuperadas pelo método get() para converter entre Kotlin e tipos de dados BSON.

No entanto, nos casos em que você adiciona uma classe que contém campos que exigem objetos Codec correspondentes, você precisa garantir que você instancia os objetos Codec para os campos da classe antes de instanciar o Codec para a classe. Você pode utilizar o parâmetro CodecRegistry no método get() para passar qualquer uma das instâncias do Codec em que o Codec confia.

O exemplo de código a seguir mostra como você pode implementar CodecProvider para passar ao MonolightCodec quaisquer instâncias Codec necessárias em uma instância CodecRegistry , como o PowerStatusCodec do nosso exemplo 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 visualizar um exemplo executável que demonstra operações de leitura e gravação utilizando estas classes do Codec, consulte a seção Exemplo de codec personalizado deste guia.

O registro de codec padrão é um definir de classes CodecProvider que especificam a conversão entre tipos Kotlin e MongoDB comumente usados. O driver usa automaticamente o registro de codec padrão, a menos que você especifique um registro diferente.

Se for necessário substituir o comportamento de uma ou mais classes Codec, mas manter o comportamento do registro de codecs padrão para as outras classes, você poderá especificar todos os registros em ordem de precedência. Por exemplo, se você quiser substituir o comportamento do provedor padrão de um Codec para tipos de enum pelo seu MyEnumCodec personalizado, deverá adicioná-lo à lista de registros antes do registro de codecs padrão, conforme mostrado no exemplo abaixo:

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

Para obter mais informações sobre as classes e interfaces nesta seção, consulte as seguintes seções de documentação da API:

A classe BsonTypeClassMap contém um mapeamento recomendado entre os tipos BSON e Kotlin. Você pode usar essa classe em seu Codec ou CodecProvider personalizado para ajudá-lo a gerenciar quais tipos de Kotlin decodificam seus BSON types para classes de contêiner que implementam Iterable ou Map, como a classe Document.

Você pode adicionar ou modificar o mapeamento padrão do BsonTypeClassMap passando um Map contendo entradas novas ou de substituição.

O seguinte trecho de código mostra como você pode recuperar o tipo de classe Kotlin que corresponde ao tipo BSON na instância BsonTypeClassMap padrão:

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

Você pode modificar esses mapeamentos na sua instância especificando substituições no construtor BsonTypeClassMap. O snippet de código a seguir mostra como você pode substituir o mapeamento de ARRAY em sua instância BsonTypeClassMap pela classe 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 obter uma lista completa dos mapeamentos padrão, consulte a documentação da API BsonTypeClassMap.

Para um exemplo de como a classe Document utiliza o BsonTypeClassMap, consulte o código-fonte do driver para as seguintes classes:

Nesta seção, mostramos como você pode implementar Codec e CodecProvider para definir a lógica de codificação e decodificação de uma classe Kotlin personalizada. Também mostramos como você pode especificar e usar suas implementações personalizadas para realizar operações de inserção e recuperação.

Dica

Serialização Kotlin

Como alternativa à implementação de codecs personalizados, você pode usar a serialização do Kotlin para lidar com a codificação e a decodificação de dados com classes @Serializable . Você pode escolher a serialização do Kotlin se já estiver familiarizado com a estrutura ou preferir usar uma abordagem idiomática do Kotlin . Consulte a documentação do Kotlin Serialization para obter mais informações.

O trecho de código a seguir mostra nosso exemplo de classe personalizada chamada Monolight e seus campos que queremos armazenar e recuperar de uma coleção do MongoDB:

data class Monolight(
var powerStatus: PowerStatus = PowerStatus.OFF,
var colorTemperature: Int? = null
) {
override fun toString(): String = "Monolight [powerStatus=$powerStatus, colorTemperature=$colorTemperature]"
}

Esta classe contém os seguintes campos, cada um dos quais precisamos atribuir um Codec:

  • powerStatus descreve se a luz está ligada ou "desligada" para a qual usamos o PowerStatusCodec que converte valores de enumeração específicos em booleanos BSON.

  • colorTemperature descreve a cor da luz e contém um valor Int para o qual usamos o IntegerCodec incluído na biblioteca BSON.

O seguinte exemplo de código mostra como podemos implementar um Codec para a classe Monolight. Observe que o construtor espera uma instância de CodecRegistry da qual ele recupera as instâncias de Codec necessárias para codificar e decodificar seus 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 garantir que disponibilizaremos as instâncias Codec dos campos para Monolight, implementamos um CodecProvider personalizado mostrado no exemplo de código a seguir:

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
}
}

Após definir a lógica de conversão, podemos realizar o seguinte:

  • Armazenar dados de instâncias de Monolight no MongoDB

  • Recuperar dados do MongoDB em instâncias de Monolight

A classe de exemplo a seguir contém código que atribui o MonolightCodecProvider à instância MongoCollection passando-o para o método withCodecRegistry() . A classe de exemplo também insere e recupera dados utilizando a classe Monolight e codecs associados:

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 obter mais informações sobre os métodos e as classes mencionadas nesta seção, consulte a seguinte documentação da API:

Voltar

Serialização Kotlin