kotlinx.serialization: o que é, anotações e serialização em JSON

Autor: IT Sectr Publicado: 2026-03-15 Tempo de leitura: 12 min

kotlinx.serialization — uma biblioteca multiplataforma da JetBrains para converter objetos Kotlin em JSON, ProtoBuf, CBOR e outros formatos sem usar reflexão. Ao contrário de Gson e Moshi, ela gera código do serializador em tempo de compilação através da anotação @Serializable, oferecendo alto desempenho e segurança de tipos. De acordo com GitHub Kotlin/kotlinx.serialization, a biblioteca suporta Kotlin/JVM, Kotlin/Native, Kotlin/JS e Kotlin/Wasm.

Pontos principais

  • kotlinx.serialization — serialização em tempo de compilação: código gerado na compilação, sem reflexão
  • @Serializable — anotação principal que ativa a geração do serializador para uma classe
  • Json {} builder — configuração JSON via Json { ignoreUnknownKeys = true; prettyPrint = true }
  • Multiplataforma — a biblioteca funciona em JVM, Native, JS e Wasm sem alterar a API
  • Serializadores personalizados — através da interface KSerializer para formatos de dados não padronizados

O que é kotlinx.serialization

kotlinx.serialization é uma biblioteca de serialização integrada para Kotlin, desenvolvida pela JetBrains como parte do ecossistema oficial do Kotlin. Sua principal diferença em relação a soluções de terceiros (Gson, Moshi, Jackson) é que ela não usa reflexão em tempo de execução. Em vez disso, o código do serializador é gerado em tempo de compilação usando Kotlin Symbol Processing (KSP) ou o plugin do compilador Kotlin. Isso proporciona um ganho de desempenho de até 3–5 vezes em comparação com Gson e garante segurança de tipos.

A biblioteca suporta oficialmente quatro formatos: JSON (através do módulo kotlinx-serialization-json), ProtoBuf (kotlinx-serialization-protobuf), CBOR (kotlinx-serialization-cbor) e HOCON (kotlinx-serialization-hocon). Os formatos são adicionados como dependências separadas no build.gradle.kts, evitando incluir bibliotecas desnecessárias no projeto. Cada formato tem seu próprio conjunto de parâmetros de configuração.

Multiplataforma é um recurso-chave da biblioteca. A mesma classe com @Serializable funciona em todos os destinos: JVM (Android, Backend), Native (iOS), JS (Web, React) e Wasm (WebAssembly). O desenvolvedor não precisa escrever implementações de serialização diferentes para cada plataforma — o código permanece o mesmo. Isso é especialmente valioso em projetos Kotlin Multiplatform Mobile (KMM) onde o código compartilhado é usado no Android e iOS.

Como funciona a geração de código em tempo de compilação

A geração de código no kotlinx.serialization ocorre em três etapas. Na primeira etapa, o compilador Kotlin detecta a anotação @Serializable em uma classe e a passa para o plugin Kotlin Symbol Processing (KSP). Na segunda etapa, o KSP gera um objeto serializador que implementa a interface KSerializer. Na terceira etapa, o código gerado é compilado junto com o código-fonte do projeto. Como resultado, nenhuma dessas etapas é executada durante o tempo de execução da aplicação.

O serializador gerado trabalha diretamente com os campos da classe através de seus getters e setters, sem reflexão. Isso significa que os campos com o modificador private também são serializados se estiverem marcados com @Serializable. O desempenho dessa abordagem é próximo à serialização manual: para classes simples (5–10 campos), o tempo de serialização é de 10–50 microssegundos; para grafos de objetos complexos, até 200 microssegundos por 1000 objetos.

Para adicionar a biblioteca a um projeto Android ou Kotlin/JVM, é necessário adicionar o plugin e as dependências no build.gradle.kts. O plugin org.jetbrains.kotlin.plugin.serialization, com a versão correspondente à do Kotlin, ativa a geração de código. A biblioteca kotlinx-serialization-json é adicionada na seção dependencies com uma versão independente da versão do Kotlin.

kotlin
// build.gradle.kts — adicionando kotlinx.serialization
plugins {
    val kotlinVersion = "2.1.0"
    kotlin("jvm") version kotlinVersion
    kotlin("plugin.serialization") version kotlinVersion
}

dependencies {
    // Módulo principal de serialização
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")

    // Formatos adicionais
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.7.3")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.7.3")
}

Uso básico: serialização em JSON

JSON é o formato mais popular no kotlinx.serialization. Para serializar um objeto, basta anotar a data class com @Serializable e chamar Json.encodeToString(). Para desserialização, chame Json.decodeFromString() especificando o tipo. A biblioteca lida automaticamente com campos null, listas, objetos aninhados e enums. Todos os campos da classe são obrigatórios por padrão, salvo indicação contrária.

A configuração JSON é feita através do Json {} builder. Você pode passar ignoreUnknownKeys = true para ignorar campos desconhecidos durante a desserialização, prettyPrint = true para saída formatada, coerceInputValues = true para converter valores inválidos em valores padrão. Também estão disponíveis encodeDefaults (serializar campos com valores padrão) e classDiscriminator (nome do campo para serialização polimórfica).

kotlin
// Exemplo de serialização e desserialização JSON
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration

@Serializable
data class Project(
    val name: String,
    val stars: Int,
    val isActive: Boolean = true,
    val languages: List<String> = emptyList()
)

fun main() {
    val project = Project(
        name = "kotlinx.serialization",
        stars = 7200,
        languages = listOf("Kotlin", "Java")
    )

    // Serialização JSON com prettyPrint
    val json = Json { prettyPrint = true }
    val jsonString = json.encodeToString(project)
    println(jsonString)
    /*
    {
        "name": "kotlinx.serialization",
        "stars": 7200,
        "isActive": true,
        "languages": ["Kotlin", "Java"]
    }
    */

    // Desserialização de JSON
    val decoded = json.decodeFromString<Project>(jsonString)
    println(decoded.name)  // kotlinx.serialization
}

O exemplo demonstra o ciclo básico de serialização e desserialização. Uma data class Project com a anotação @Serializable obtem automaticamente encodeToString e decodeFromString. O campo isActive tem um valor padrão true — se este campo estiver ausente no JSON, o valor padrão é usado. Se campos desconhecidos chegarem no JSON sem ignoreUnknownKeys = true, uma SerializationException é lançada.

Serialização polimórfica de sealed class

Sealed class é um dos casos de uso mais poderosos do kotlinx.serialization. A biblioteca suporta serialização polimórfica para hierarquias de sealed class sem configuração adicional: basta anotar a sealed class e todas as suas subclasses com @Serializable. Durante a serialização, um campo “type” é adicionado (configurável via classDiscriminator), que determina o tipo específico durante a desserialização.

kotlin
// Serialização polimórfica de sealed class
@Serializable
sealed class Response

@Serializable
data class Success(val data: String) : Response()

@Serializable
data class Error(val code: Int, val message: String) : Response()

fun main() {
    val json = Json { classDiscriminator = "result_type" }

    val responses: List<Response> = listOf(
        Success(data = "Data loaded"),
        Error(code = 404, message = "Not found")
    )

    val jsonString = json.encodeToString(responses)
    println(jsonString)
    /*
    [
        {"result_type":"Success","data":"Data loaded"},
        {"result_type":"Error","code":404,"message":"Not found"}
    ]
    */

    val decoded = json.decodeFromString<List<Response>>(jsonString)
    when (val first = decoded[0]) {
        is Success -> println("Success: ${first.data}")
        is Error -> println("Error: ${first.code}")
    }
}

A serialização polimórfica de sealed class é especialmente útil em clientes de API onde o servidor retorna diferentes tipos de resposta. Sem o kotlinx.serialization, seria necessário escrever um desserializador manual com when no campo discriminador. Com a biblioteca, isso é feito com uma única anotação. classDiscriminator permite renomear o campo marcador (padrão “type”) para qualquer valor esperado pelo servidor.

Anotações do kotlinx.serialization: visão completa

A biblioteca fornece um conjunto de anotações para ajustar finamente a serialização. A principal é @Serializable para uma classe. Adicionais: @SerialName para definir o nome de um campo no JSON (se diferente do nome Kotlin), @Transient para excluir um campo da serialização, @Required para um campo que deve estar presente no JSON, @EncodeDefault para forçar a serialização de um campo mesmo com seu valor padrão.

AnotaçãoPropósitoExemplo
@SerializableAtiva a geração do serializador para uma classe@Serializable data class User
@SerialNameDefine um nome alternativo para o campo no formato@SerialName(“user_name”) val name: String
@TransientExclui um campo da serialização@Transient val cache: MutableMap
@RequiredCampo obrigatório no JSON durante a desserialização@Required val id: String
@EncodeDefaultSerializa o campo mesmo com valor padrão@EncodeDefault val type: Type = Type.A
@SerializerVincula um serializador personalizado a uma classe@Serializer(forClass = Date::class)

A anotação @SerialName é crítica ao trabalhar com APIs onde os nomes dos campos estão em snake_case, enquanto o estilo Kotlin é camelCase. Por exemplo, o servidor envia “user_id”, mas no código Kotlin é usado userId. @SerialName(“user_id”) resolve esse problema sem mapeadores adicionais. @Transient é útil para campos que não devem ser enviados ao servidor — por exemplo, valores calculados temporários ou caches.

@Required como alternativa a campos nullable

Por padrão, todos os campos no kotlinx.serialization são obrigatórios. Se um campo pode estar ausente no JSON, é preciso torná-lo nullable (String?) ou definir um valor padrão (val name: String = “”). No entanto, há situações em que um campo é non-nullable no Kotlin mas pode estar ausente no JSON devido ao versionamento da API. Nesse caso, @Required lança uma SerializationException quando o campo está ausente, enquanto um valor padrão o preenche sem erro.

Serializadores personalizados: KSerializer e controle manual

KSerializer é a interface que todos os serializadores no kotlinx.serialization implementam. Se a geração de código padrão não for adequada (por exemplo, para trabalhar com Date, Bitmap ou um formato binário específico), você pode escrever seu próprio serializador. Para isso, implemente os métodos serialize() e deserialize(), e forneça um descritor — uma descrição da estrutura para o esquema do formato.

Serializadores personalizados são conectados de duas maneiras: através da anotação @Serializable(with = MySerializer::class) para vincular a uma classe específica, ou globalmente via Json { serializersModule = ... } para vincular a todas as instâncias de um tipo. O segundo método é preferível para tipos integrados (Date, UUID) para evitar escrever uma anotação em cada campo.

kotlin
// Serializador personalizado para java.util.Date
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

object DateSerializer : KSerializer<Date> {
    private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)

    override val descriptor: SerialDescriptor =
        PrimitiveSerialDescriptor("Date", PrimitiveKind.STRING)

    override fun serialize(encoder: Encoder, value: Date) {
        encoder.encodeString(dateFormat.format(value))
    }

    override fun deserialize(decoder: Decoder): Date {
        return dateFormat.parse(decoder.decodeString())
    }
}

// Usando um serializador personalizado
@Serializable
data class Event(
    val title: String,
    @Serializable(with = DateSerializer::class)
    val date: Date
)

fun main() {
    val json = Json { prettyPrint = true }
    val event = Event("Release", Date())
    println(json.encodeToString(event))
}

No exemplo, DateSerializer converte java.util.Date para uma string ISO 8601. Sem um serializador personalizado, o kotlinx.serialization não pode trabalhar com Date — é um tipo que não faz parte da biblioteca padrão do Kotlin. @Serializable(with = DateSerializer::class) em um campo específico vincula o serializador apenas para aquele campo. Para registro global de todos os Date, use Json { serializersModule = SerializersModule { contextual(DateSerializer) } }.

Formatos de serialização: JSON, ProtoBuf, CBOR, HOCON

kotlinx.serialization não se limita ao JSON. A biblioteca suporta quatro formatos integrados, cada um com seu próprio módulo e configuração. JSON (kotlinx-serialization-json) é universal, legível por humanos, adequado para APIs REST. ProtoBuf (kotlinx-serialization-protobuf) é binário, compacto, com esquema obrigatório, para microsserviços de alta carga. CBOR (kotlinx-serialization-cbor) é um equivalente binário do JSON, conveniente para IoT e dispositivos móveis com largura de banda limitada. HOCON (kotlinx-serialization-hocon) é um formato de configuração compatível com TypeSafe Config.

FormatoMóduloTipoEsquemaUso típico
JSONkotlinx-serialization-jsonTextoOpcionalAPI REST, armazenamento de dados
ProtoBufkotlinx-serialization-protobufBinárioObrigatório (.proto)Microsserviços, gRPC
CBORkotlinx-serialization-cborBinárioOpcionalIoT, dispositivos móveis
HOCONkotlinx-serialization-hoconTextoOpcionalArquivos de configuração

ProtoBuf requer a definição de um esquema em arquivos .proto, mas o kotlinx-serialization-protobuf gera classes Kotlin diretamente de @Serializable sem .proto. Isso simplifica o desenvolvimento: basta anotar a data class e usar ProtoBuf.encodeToByteArray(). CBOR é especialmente relevante para Android quando é necessário transferir dados binários compactos via NFC ou BLE. As mensagens CBOR são em média 20–30% menores que JSON para o mesmo conjunto de dados.

Escolha do formato para o projeto

Para APIs REST em um aplicativo móvel, JSON é a melhor escolha — pode ser depurado sem ferramentas adicionais, é legível em logs e compatível com qualquer backend. Se o aplicativo transferir grandes volumes de dados entre microsserviços (centenas de megabytes), o ProtoBuf oferece uma vantagem de velocidade de até 5x devido à codificação binária. Para armazenar configurações em arquivos, use HOCON ou JSON. Para dispositivos com limites rigorosos de tráfego (sensores IoT), use CBOR.

Erros comuns ao trabalhar com kotlinx.serialization

O primeiro erro é ignorar chaves desconhecidas durante a desserialização. Se o servidor adicionar um novo campo e você tiver ignoreUnknownKeys = false, o aplicativo falhará com uma SerializationException. Essa flag está desativada por padrão. Solução: sempre configure Json { ignoreUnknownKeys = true } para código de produção, para ser resiliente a mudanças na API.

O segundo erro é serializar campos internal ou private em uma data class. Em uma data class do Kotlin, todos os campos no construtor primário são serializados por padrão. Se um campo contiver dados sensíveis (senha, token), ele deve ser marcado com @Transient ou removido do construtor primário. @Transient exclui o campo do JSON completamente, mas dentro do construtor pode causar um erro — é melhor definir esses campos no corpo da classe com @Transient.

O terceiro erro é a serialização polimórfica sem sealed class. Se você usar uma open class em vez de sealed, o kotlinx.serialization exige o registro explícito de todas as subclasses no serializersModule. Ao contrário das sealed class, onde o compilador conhece todas as subclasses, as open class permitem extensão arbitrária — a biblioteca não pode determinar automaticamente todos os subtipos. O registro é feito através de Json { serializersModule = SerializersModule { polymorphic(Base::class) { subclass(Derived::class) } } }.

Erro de versionamento da biblioteca

A versão do kotlinx.serialization deve ser compatível com a versão do Kotlin. A JetBrains publica uma tabela de compatibilidade: kotlinx-serialization 1.6.x é compatível com Kotlin 1.9.x, 1.7.x com Kotlin 2.0.x e 2.1.x. A incompatibilidade de versões causa erros de compilação enigmáticos como “Symbol ‘serializer’ is missing”. Sempre verifique a versão mais recente no Maven Central ou no repositório GitHub do projeto.

Perguntas frequentes

Como o kotlinx.serialization difere do Gson e Moshi?

kotlinx.serialization usa geração de código em tempo de compilação via KSP, enquanto Gson e Moshi usam reflexão em tempo de execução. Isso oferece uma vantagem de desempenho (3–5x mais rápido que Gson) e segurança de tipos. Gson serializa qualquer campo sem anotação, o que pode levar a vazamentos de dados. kotlinx.serialization exige a anotação explícita @Serializable, sendo mais seguro. Moshi também suporta codegen, mas apenas para JVM e Android.

O kotlinx.serialization suporta Kotlin Multiplatform?

Sim, o kotlinx.serialization é uma biblioteca multiplataforma oficial da JetBrains. Funciona em Kotlin/JVM (Android, Backend), Kotlin/Native (iOS), Kotlin/JS (Web, React) e Kotlin/Wasm. A API é unificada em todas as plataformas: @Serializable + Json.encodeToString() funciona da mesma forma em todos os lugares. Para iOS, nenhuma configuração adicional é necessária — o Kotlin/Native compila o código serializado em um binário nativo.

Como os campos null são tratados no JSON?

Campos nullable (String?) são desserializados como null se o valor estiver ausente ou for null no JSON. Para campos non-nullable (String) sem valor padrão, a ausência do campo no JSON lançará uma SerializationException. Se você quiser que valores null não apareçam no JSON, configure Json { encodeDefaults = false }. Isso exclui todos os campos iguais ao padrão (incluindo null para tipos nullable).

O que fazer se o servidor enviar campos em snake_case?

Use @SerialName(“nome_em_snake_case”) em cada campo cujo nome difere do formato Kotlin. Alternativamente, para Kotlin 2.0+, está disponível Json { namingStrategy = JsonNamingStrategy.SnakeCase } para conversão automática camelCase ↔ snake_case. Esta configuração se aplica a todos os campos de uma vez. Se for necessária personalização parcial, combine @SerialName com a estratégia global.

É possível serializar Kotlin Flow ou corrotinas?

Não, Flow e corrotinas não são diretamente serializáveis — eles representam execução assíncrona, não dados. Para transferir dados de um Flow, colete-os em uma coleção através de .toList() em uma corrotina e serializa a coleção. Da mesma forma, você não pode serializar Job, Deferred ou Continuation. Serialize apenas data classes — modelos de dados sem lógica comportamental.

Resumo

  • kotlinx.serialization — serialização em tempo de compilação via @Serializable, sem reflexão, até 5x mais rápido que Gson
  • @Serializable, @SerialName, @Transient — anotações principais para configurar serialização de campos e classes
  • Json {} builder configura JSON: ignoreUnknownKeys, prettyPrint, coerceInputValues, encodeDefaults
  • Sealed class e serialização polimórfica — suporte contínuo a hierarquias de tipo sem código adicional
  • KSerializer — interface para serializadores personalizados de tipos não padronizados (Date, Bitmap, UUID)
  • Quatro formatos: JSON, ProtoBuf, CBOR, HOCON — adicionados como módulos, API unificada para todos
  • Multiplataforma — base de código única para JVM, Native, JS e Wasm; crítico para KMM e módulos compartilhados

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também