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 é 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.
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.
// 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")
}
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).
// 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.
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.
// 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.
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ção | Propósito | Exemplo |
|---|---|---|
| @Serializable | Ativa a geração do serializador para uma classe | @Serializable data class User |
| @SerialName | Define um nome alternativo para o campo no formato | @SerialName(“user_name”) val name: String |
| @Transient | Exclui um campo da serialização | @Transient val cache: MutableMap |
| @Required | Campo obrigatório no JSON durante a desserialização | @Required val id: String |
| @EncodeDefault | Serializa o campo mesmo com valor padrão | @EncodeDefault val type: Type = Type.A |
| @Serializer | Vincula 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.
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.
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.
// 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) } }.
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.
| Formato | Módulo | Tipo | Esquema | Uso típico |
|---|---|---|---|---|
| JSON | kotlinx-serialization-json | Texto | Opcional | API REST, armazenamento de dados |
| ProtoBuf | kotlinx-serialization-protobuf | Binário | Obrigatório (.proto) | Microsserviços, gRPC |
| CBOR | kotlinx-serialization-cbor | Binário | Opcional | IoT, dispositivos móveis |
| HOCON | kotlinx-serialization-hocon | Texto | Opcional | Arquivos 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.
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.
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) } } }.
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
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.
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.
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).
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.
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
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.
Leia também