O que é serialization em Kotlin?
Resposta rápida: serialization (serialização) transforma um objeto Kotlin em dados que podem ser enviados por uma API ou salvos, normalmente JSON. A desserialização faz o inverso: lê os dados e cria novamente um objeto tipado. A biblioteca oficial kotlinx.serialization usa @Serializable e um plugin do compilador para gerar serializers sem depender de reflexão em runtime. Ela funciona em projetos Android, backend com Ktor e Kotlin Multiplatform.
@Serializable
data class Usuario(val nome: String, val idade: Int)
val texto = Json.encodeToString(Usuario("Ana", 30))
val usuario = Json.decodeFromString<Usuario>(texto)
| Se você precisa de… | Use |
|---|---|
| JSON em Android, backend ou KMP | kotlinx-serialization-json |
| Modelos com nomes de campos diferentes da API | @SerialName |
| Ignorar novos campos enviados por uma API | Json { ignoreUnknownKeys = true } |
| Variantes fechadas de um mesmo modelo | sealed class + @Serializable |
| Um tipo externo ou formato especial | KSerializer<T> customizado |
| Formato binário compacto | ProtoBuf ou CBOR |
Configuração do projeto
O plugin de serialização deve usar a mesma versão do Kotlin configurada no projeto. A biblioteca JSON tem versionamento próprio; substitua os marcadores abaixo pelas versões compatíveis escolhidas no seu catálogo ou arquivo de build:
// build.gradle.kts
plugins {
kotlin("jvm") version "<versao-do-kotlin>"
kotlin("plugin.serialization") version "<mesma-versao-do-kotlin>"
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:<versao-da-biblioteca>")
}
Em Android, mantenha o plugin Android já usado pelo módulo e acrescente kotlin("plugin.serialization"). Em projetos KMP, declare a dependência no commonMain quando os modelos forem compartilhados. Confirme as versões atuais na documentação oficial antes de atualizar um projeto existente.
Uso básico com JSON
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
data class Usuario(
val nome: String,
val idade: Int,
val email: String
)
fun main() {
val usuario = Usuario("Ana", 30, "ana@email.com")
// Serializar: objeto -> JSON string
val json = Json.encodeToString(usuario)
println(json)
// {"nome":"Ana","idade":30,"email":"ana@email.com"}
// Desserializar: JSON string -> objeto
val objeto = Json.decodeFromString<Usuario>(json)
println(objeto)
// Usuario(nome=Ana, idade=30, email=ana@email.com)
}
A anotação @Serializable instrui o plugin do compilador a gerar um serializer para a classe. Sem ela — ou sem um serializer informado explicitamente — a classe não pode ser serializada. Esse padrão combina especialmente bem com data classes, mas também funciona com classes comuns, objetos, enums e sealed classes.
Customizando nomes de campos
@Serializable
data class Produto(
@SerialName("nome_produto")
val nome: String,
@SerialName("preco_unitario")
val preco: Double,
@SerialName("em_estoque")
val emEstoque: Boolean
)
fun main() {
val produto = Produto("Teclado", 299.90, true)
val json = Json.encodeToString(produto)
println(json)
// {"nome_produto":"Teclado","preco_unitario":299.9,"em_estoque":true}
}
Valores opcionais e padrão
@Serializable
data class Configuração(
val tema: String = "claro",
val fontSize: Int = 14,
val idioma: String = "pt-BR"
)
fun main() {
val json = Json { encodeDefaults = false }
// Serializar: campos com valor padrao podem ser omitidos
val config = Configuração()
println(json.encodeToString(config))
// {} (campos com valor padrao omitidos)
// Desserializar: campos ausentes usam valor padrao
val configParcial = json.decodeFromString<Configuração>("""{"tema":"escuro"}""")
println(configParcial)
// Configuração(tema=escuro, fontSize=14, idioma=pt-BR)
}
Configurando o Json
O objeto Json pode ser customizado com várias opções:
val jsonConfig = Json {
prettyPrint = true // JSON formatado com indentacao
isLenient = true // Aceita JSON nao estritamente valido
ignoreUnknownKeys = true // Ignora campos desconhecidos
encodeDefaults = true // Inclui campos com valor padrao
coerceInputValues = true // Coerce valores incorretos para padrao
explicitNulls = false // Omite campos null
namingStrategy = JsonNamingStrategy.SnakeCase // Converte camelCase para snake_case
}
@Serializable
data class Resposta(
val statusCode: Int,
val mensagem: String,
val dados: String? = null
)
fun main() {
val json = """
{
"status_code": 200,
"mensagem": "Sucesso",
"campo_extra": "ignorado"
}
""".trimIndent()
val resposta = jsonConfig.decodeFromString<Resposta>(json)
println(resposta)
}
Serializando coleções e tipos aninhados
@Serializable
data class Endereco(
val rua: String,
val cidade: String,
val estado: String
)
@Serializable
data class Pedido(
val id: Long,
val itens: List<String>,
val endereco: Endereco,
val tags: Set<String> = emptySet(),
val metadados: Map<String, String> = emptyMap()
)
fun main() {
val pedido = Pedido(
id = 1001,
itens = listOf("Teclado", "Mouse"),
endereco = Endereco("Rua A", "São Paulo", "SP"),
tags = setOf("eletronicos", "informatica"),
metadados = mapOf("prioridade" to "alta")
)
val json = Json { prettyPrint = true }
println(json.encodeToString(pedido))
}
Polimorfismo com sealed classes
@Serializable
sealed class Notificacao {
@Serializable
@SerialName("email")
data class Email(val destinatario: String, val assunto: String) : Notificacao()
@Serializable
@SerialName("push")
data class Push(val token: String, val titulo: String) : Notificacao()
@Serializable
@SerialName("sms")
data class Sms(val telefone: String, val mensagem: String) : Notificacao()
}
fun main() {
val notificacoes: List<Notificacao> = listOf(
Notificacao.Email("ana@email.com", "Bem-vinda"),
Notificacao.Push("token123", "Nova mensagem"),
Notificacao.Sms("+5511999999999", "Código: 1234")
)
val json = Json { prettyPrint = true }
val jsonStr = json.encodeToString(notificacoes)
println(jsonStr)
// Desserializa com o tipo correto baseado no discriminador
val desserializado = json.decodeFromString<List<Notificacao>>(jsonStr)
desserializado.forEach { println(it::class.simpleName) }
}
Serializer customizado
Para tipos que não são controlados por você ou que precisam de formatação especial:
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import java.time.LocalDate
object LocalDateSerializer : KSerializer<LocalDate> {
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: LocalDate) {
encoder.encodeString(value.toString())
}
override fun deserialize(decoder: Decoder): LocalDate {
return LocalDate.parse(decoder.decodeString())
}
}
@Serializable
data class Evento(
val nome: String,
@Serializable(with = LocalDateSerializer::class)
val data: LocalDate
)
Outros formatos além de JSON
kotlinx.serialization suporta múltiplos formatos:
// Protobuf
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.6.2")
@Serializable
data class Mensagem(@ProtoNumber(1) val texto: String, @ProtoNumber(2) val id: Int)
val bytes = ProtoBuf.encodeToByteArray(Mensagem("Ola", 1))
val msg = ProtoBuf.decodeFromByteArray<Mensagem>(bytes)
// CBOR
implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.6.2")
val cborBytes = Cbor.encodeToByteArray(usuario)
Quando usar kotlinx.serialization
- APIs REST: serializar e desserializar JSON em chamadas HTTP com Ktor ou Retrofit.
- Armazenamento local: salvar estado e configurações em arquivos ou no DataStore quando JSON for adequado ao caso.
- Kotlin Multiplatform: compartilhar modelos e serializers entre targets sem limitar o domínio à JVM.
- Apps Android com R8: evitar uma solução baseada apenas em reflexão e manter contratos Kotlin explícitos.
- Comunicação entre serviços: usar ProtoBuf ou CBOR quando um formato binário fizer sentido para o contrato.
A escolha não é automática. A comparação direta ajuda a decidir:
kotlinx.serialization vs Gson vs Moshi vs Jackson
| Critério | kotlinx.serialization | Gson | Moshi | Jackson |
|---|---|---|---|---|
| Geração em tempo de compilação | Sim (plugin do compilador) | Não (reflexão) | Opcional (codegen) | Não (reflexão) |
| Kotlin Multiplatform | Sim — JVM, Android, iOS, JS, Wasm | Não (JVM/Android) | Não (JVM/Android) | Não (JVM) |
| Valores padrão e nullability do Kotlin | Nativos | Parcial (perde defaults) | Parcial | Parcial |
| Sealed classes polimórficas | Nativas via @Serializable | Não | Requer adapters | Requer módulos extras |
| Custo em builds Android com R8 | Baixo (sem reflexão) | Médio | Baixo com codegen | Alto |
| Ecossistema típico | Kotlin, Ktor, KMP | Legado Android | Android moderno | Spring/JVM |
Jackson continua forte em aplicações Spring/JVM, enquanto Moshi é comum no Android e oferece geração de adapters. Prefira kotlinx.serialization quando integração com Kotlin, multiplataforma, modelos imutáveis e geração em tempo de compilação forem prioridades.
Casos de Uso no Mundo Real
comunicação com APIs REST: em aplicações Android e backend com Ktor, kotlinx.serialization e usado para converter respostas JSON de APIs em data classes Kotlin e vice-versa. A integração com Ktor e nativa, dispensando configuração adicional para serialização de requisicoes e respostas.
Armazenamento local estruturado: aplicações que precisam salvar estado ou configuracoes em disco usam kotlinx.serialization para converter objetos em JSON e gravar em arquivos ou DataStore. Na leitura, os dados sao desserializados de volta para objetos tipados, garantindo type safety.
Kotlin Multiplatform (KMP): em projetos multiplataforma que compartilham código entre Android, iOS, web e desktop, kotlinx.serialization e a única biblioteca de serialização que funciona em todos os targets. Modelos de dados compartilhados sao anotados com
@Serializableuma única vez e funcionam em todas as plataformas.comunicação entre microsservicos: backends Kotlin que se comunicam via gRPC ou mensageria (Kafka, RabbitMQ) usam formatos binarios como Protobuf ou CBOR com kotlinx.serialization para obter serialização eficiente e compacta, reduzindo latencia e consumo de banda.
Boas Praticas
- Configure
ignoreUnknownKeys = trueao consumir APIs externas: APIs de terceiros podem adicionar campos novos a qualquer momento. Sem essa configuração, campos desconhecidos causam exceção e quebram sua aplicação. - Use
@SerialNamepara desacoplar nomes Kotlin de nomes JSON: mantenha nomes de propriedades idiomaticos em Kotlin (camelCase) e use@SerialNamepara mapear para o formato da API (snake_case ou outro), evitando dependência direta do formato externo. - Crie uma instancia reutilizavel de
Jsoncom suas configuracoes: em vez de criar uma nova instancia a cada serialização, defina um objetoJsonconfigurado uma vez e reutilize-o em toda a aplicação. Isso melhora performance e garante consistencia. - Prefira
encodeDefaults = falsepara payloads menores: quando campos com valor padrão não precisam ser enviados, desativar a codificacao de defaults reduz o tamanho do JSON e o trafego de rede. - Valide dados desserializados apos a conversao: kotlinx.serialization garante que o JSON e válido e que os tipos estao corretos, mas não válida regras de negócio. Adicione validacao explicita (como verificar se um email tem formato válido) apos a desserializacao.
Perguntas Frequentes
P: Qual a diferenca entre kotlinx.serialization e Gson/Jackson/Moshi? R: kotlinx.serialization gera código de serialização em tempo de compilação, enquanto Gson e Jackson usam reflexao em tempo de execução. Isso torna kotlinx.serialization mais rápida, type-safe e compativel com Kotlin Multiplatform. Alem disso, kotlinx.serialization entende nativamente recursos do Kotlin como valores padrão, nullability e sealed classes.
P: Posso usar kotlinx.serialization com Retrofit?
R: Sim, por meio de uma Converter.Factory compatível com a versão do Retrofit usada no projeto. Verifique a documentação e a manutenção do converter antes de adotá-lo: bibliotecas comunitárias e integrações mudam de compatibilidade ao longo do tempo. Com Ktor, a integração é fornecida pelo ecossistema oficial via ContentNegotiation e ktor-serialization-kotlinx-json. Veja também o tutorial de Retrofit em Kotlin.
P: Como serializo classes que não posso anotar com @Serializable (classes de terceiros)?
R: Voce pode criar um KSerializer customizado para o tipo e registra-lo no modulo de serialização usando SerializersModule. Outra opção e criar uma classe wrapper ou surrogate anotada com @Serializable que represente os mesmos dados.
P: kotlinx.serialization funciona com tipos genericos?
R: Sim, mas com uma restricao: você precisa fornecer o serializer explicitamente para tipos genericos, pois o Kotlin não mantém informação de tipo generico em tempo de execução (type erasure). Use serializer<MeuTipo<Param>>() ou passe o serializer como parametro.
Erros comuns
Esquecer a anotacao @Serializable: sem ela, o plugin não gera o serializer e você recebe um erro em tempo de execução.
Nao aplicar o plugin do compilador: apenas adicionar a dependência não basta. O plugin
kotlin("plugin.serialization")deve ser aplicado no build.gradle.kts.Ignorar campos desconhecidos sem configurar: por padrão, campos extras no JSON causam exceção. Use
ignoreUnknownKeys = truequando consumir APIs externas.Confundir com Gson ou Jackson: kotlinx.serialization tem API e anotações proprias. Misturar anotações de Gson com kotlinx.serialization não funciona.
Nao tratar nulls e ausentes: em JSON, um campo pode estar ausente ou ter valor
null. ConfigureexplicitNullsecoerceInputValuesconforme necessário.
Próximos passos
- Serialização avançada: polimorfismo e SerializersModule — aprofunda o próximo nível do tema
- Data class — o modelo conciso por trás dos payloads
- Sealed class — hierarquias fechadas para serialização polimórfica
- Tutorial de Retrofit em Kotlin — consumir APIs com kotlinx.serialization no Android
- Criando APIs com Ktor — ContentNegotiation com JSON no backend
- Kotlin Multiplatform — modelos compartilhados entre plataformas
Termos relacionados
- JSON: formato textual amplamente usado em APIs web.
- Data Class: modelo conciso para representar os dados serializados.
- Sealed Class: hierarquia fechada útil na serialização polimórfica.
- Kotlin Multiplatform: permite compartilhar modelos e serializers entre plataformas.
- Ktor: framework com integração oficial para conteúdo JSON via kotlinx.serialization.
- Serialização avançada: aprofunda polimorfismo,
SerializersModulee serializers customizados.
Para começar, configure o plugin, anote um modelo com @Serializable e teste o ciclo completo encodeToString → decodeFromString. Depois, defina conscientemente como o projeto trata campos desconhecidos, valores padrão, null, evolução de schema e erros de entrada. Essa disciplina importa mais do que apenas escolher uma biblioteca.