Kotlin: Serialização Kotlin Explicada
Última atualização: 2026-08-26
kotlinx.serialization gera serializadores em tempo de compilação via um plugin de compilador — a conversão de Pedido ↔ JSON do Charlie é sem reflexão, segura em tipos e mais rápida que Jackson. @SerialName trata do mapeamento de campos em uma linha; Json { ignoreUnknownKeys = true } configura tolerância a falhas em uma linha.
1. O que Você Aprenderá
- Plugin de compilador
@Serializablegerando serializadores em tempo de compilação - Codificação/decodificação JSON:
encodeToString/decodeFromString<T> - Campos opcionais e padrões:
@SerialName/@Required - Suporte a múltiplos formatos: JSON / ProtoBuf / CBOR / HOCON
- Charlie em ação: Pedido ↔ JSON + mapeamento de campos + configuração tolerante a falhas
2. A História Real de um Desenvolvedor
(1) Problema: Bombas em Tempo de Execução na Serialização Baseada em Reflexão
Bob usava o modo de reflexão do Jackson para serializar objetos Pedido. Durante uma refatoração, orderId foi renomeado para id — a desserialização JSON falhou silenciosamente (incompatibilidade de nome de campo), e 500 registros de pedidos foram perdidos.
(2) Solução de Serialização em Tempo de Compilação
// Jackson: baseado em reflexão, erros em tempo de execução
@JsonAlias("order_id") // Fácil de esquecer
data class Order(val orderId: String, ...)
// kotlinx.serialization: tempo de compilação, erros em tempo de compilação
@Serializable
data class Order(
@SerialName("order_id") val id: String, // Compilador verifica!
val total: Double
)
Geração de serializadores em tempo de compilação — mudanças de nome de campo se tornam erros de compilação, não perda de dados em tempo de execução.
3. @Serializable e o Plugin de Compilador
(1) Configuração Gradle
// build.gradle.kts
plugins {
kotlin("jvm") version "1.9.22"
kotlin("plugin.serialization") version "1.9.22" // Plugin de serialização do compilador
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
}
(2) Serialização Básica
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
@Serializable
data class Order(val id: String, val total: Double, val status: String)
// Serializar: Objeto -> String JSON
val order = Order("ORD-001", 299.99, "CONFIRMED")
val json = Json.encodeToString(order)
// {"id":"ORD-001","total":299.99,"status":"CONFIRMED"}
// Desserializar: String JSON -> Objeto
val decoded = Json.decodeFromString<Order>(json)
// Order(id=ORD-001, total=299.99, status=CONFIRMED)
(3) Reflexão vs Serialização em Tempo de Compilação
| Dimensão | Jackson (Reflexão) | kotlinx.serialization |
|---|---|---|
| Mecanismo | Reflexão em tempo de execução | Geração de código em tempo de compilação |
| Segurança | Erros em tempo de execução | Erros em tempo de compilação |
| Performance | Mais lento | Mais rápido (sem overhead de reflexão) |
| ProGuard | Requer regras keep | Não necessário |
| Multiplataforma | Somente JVM | JVM / Native / JS |
4. Codificação e Decodificação JSON
(1) Configuração Json
// Padrão: modo estrito
val strictJson = Json // Falha em chaves desconhecidas
// Tolerante: ignora chaves desconhecidas (versionamento de API)
val lenientJson = Json {
ignoreUnknownKeys = true // Ignora campos não presentes na classe
isLenient = true // Aceita JSON malformado
encodeDefaults = true // Inclui campos com valores padrão
prettyPrint = true // Impressão formatada
prettyPrintIndent = " " // Indentação
coerceInputValues = true // Usa padrão para null em campo não-nulo
}
val json = lenientJson.encodeToString(order)
(2) Opções de Configuração Json
| Opção | Padrão | Descrição |
|---|---|---|
ignoreUnknownKeys |
false | Ignora campos JSON não presentes na classe |
isLenient |
false | Análise tolerante (aceita JSON não padrão) |
encodeDefaults |
false | Codifica campos com valores padrão |
prettyPrint |
false | Saída formatada |
coerceInputValues |
false | Usa valor padrão quando campo não-nulo recebe null |
5. Mapeamento de Campos e Padrões
(1) Mapeamento de Campos com @SerialName
@Serializable
data class Order(
@SerialName("order_id") val id: String, // JSON: order_id
@SerialName("order_total") val total: Double, // JSON: order_total
val status: String = "PENDING" // JSON: status (mesmo nome)
)
// JSON: {"order_id":"ORD-001","order_total":299.99,"status":"CONFIRMED"}
(2) Campos Opcionais e Padrões
@Serializable
data class Order(
@SerialName("order_id") val id: String,
@SerialName("order_total") val total: Double,
val status: String = "PENDING", // Opcional com padrão
val customer: String? = null, // Opcional anulável
@SerialName("tax_rate") val taxRate: Double = 0.08
)
// Campos opcionais ausentes usam padrões
val json = """{"order_id":"ORD-001","order_total":299.99}"""
val order = Json.decodeFromString<Order>(json)
// Order(id=ORD-001, total=299.99, status=PENDING, customer=null, taxRate=0.08)
(3) @Required para Campos Obrigatórios
@Serializable
data class Order(
@Required val id: String, // DEVE estar presente no JSON
@Required val total: Double // DEVE estar presente no JSON
)
// Ausência de 'id' ou 'total' -> SerializationException
6. Suporte a Múltiplos Formatos
// ProtoBuf
import kotlinx.serialization.protobuf.ProtoBuf
val protoBytes = ProtoBuf.encodeToByteArray(order)
val fromProto = ProtoBuf.decodeFromByteArray<Order>(protoBytes)
// CBOR
import kotlinx.serialization.cbor.Cbor
val cborBytes = Cbor.encodeToByteArray(order)
// HOCON (formato de configuração)
import kotlinx.serialization.hocon.Hocon
(1) Comparação de Formatos
| Formato | Legibilidade | Tamanho | Velocidade | Caso de Uso |
|---|---|---|---|---|
| JSON | Alta | Grande | Média | Comunicação de API |
| ProtoBuf | Baixa (binário) | Pequeno | Rápida | RPC de alta performance |
| CBOR | Baixa (binário) | Médio | Rápida | IoT / embarcados |
| HOCON | Alta | Médio | Média | Arquivos de configuração |
7. Fluxo de Codificação/Decodificação da Serialização
sequenceDiagram
participant Obj as Objeto Order
participant Ser as Serializador
participant JSON as String JSON
Note over Obj,Ser: Codificação
Obj->>Ser: propriedades @Serializable
Ser->>JSON: encodeToString()
Note over JSON: {"order_id":"ORD-001",...}
Note over Ser,Obj: Decodificação
JSON->>Ser: decodeFromString<Order>(json)
Ser->>Obj: construtor @Serializable
Note over Obj: Order(id=ORD-001,...)
8. Exemplo Completo: Serialização JSON do OrderProcessor
// ============================================
// OrderProcessor - Serialização JSON
// Funcionalidade: Pedido <-> JSON com mapeamento de campos
// ============================================
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
data class Address(
val street: String,
val city: String,
val country: String
)
@Serializable
data class OrderItem(
val sku: String,
val quantity: Int,
@SerialName("unit_price") val unitPrice: Double
) {
val subtotal: Double get() = quantity * unitPrice
}
@Serializable
data class Order(
@SerialName("order_id") val id: String,
@SerialName("order_total") val total: Double,
val status: String = "PENDING",
val customer: String? = null,
val items: List<OrderItem> = emptyList(),
val address: Address? = null,
@SerialName("tax_rate") val taxRate: Double = 0.08,
@SerialName("created_at") val createdAt: String = "2026-01-01T00:00:00Z"
)
val orderJson = Json {
ignoreUnknownKeys = true
encodeDefaults = true
prettyPrint = true
prettyPrintIndent = " "
}
fun main() {
// Criar pedido
val order = Order(
id = "ORD-001",
total = 299.99,
status = "CONFIRMED",
customer = "Alice",
items = listOf(
OrderItem("SKU-WIDGET", 3, 9.99),
OrderItem("SKU-GADGET", 1, 149.99)
),
address = Address("123 Main St", "New York", "US"),
createdAt = "2026-07-13T10:30:00Z"
)
// Serializar: Pedido -> JSON
println("=== Serializar ===")
val jsonString = orderJson.encodeToString(order)
println(jsonString)
// Desserializar: JSON -> Pedido
println("\n=== Desserializar ===")
val decoded = orderJson.decodeFromString<Order>(jsonString)
println("Pedido: ${decoded.id}, Total: \$${decoded.total} USD")
println("Itens: ${decoded.items.map { "${it.sku} x${it.quantity}" }}")
// Tratar chaves desconhecidas (versionamento de API)
println("\n=== Versionamento de API ===")
val jsonWithExtraFields = """
{
"order_id": "ORD-002",
"order_total": 1500.00,
"status": "SHIPPED",
"unknown_field": "isso é ok",
"new_api_version": 2
}
""".trimIndent()
val decodedWithExtra = orderJson.decodeFromString<Order>(jsonWithExtraFields)
println("Desserializado com campos extras: ${decodedWithExtra.id}")
// JSON mínimo (apenas campos obrigatórios)
println("\n=== JSON Mínimo ===")
val minimalJson = """{"order_id":"ORD-003","order_total":45.50}"""
val minimal = orderJson.decodeFromString<Order>(minimalJson)
println("Mínimo: ${minimal.id}, Status: ${minimal.status}, Cliente: ${minimal.customer}")
}
Saída:
=== Serializar ===
{
"order_id": "ORD-001",
"order_total": 299.99,
"status": "CONFIRMED",
"customer": "Alice",
"items": [
{
"sku": "SKU-WIDGET",
"quantity": 3,
"unit_price": 9.99
},
{
"sku": "SKU-GADGET",
"quantity": 1,
"unit_price": 149.99
}
],
"address": {
"street": "123 Main St",
"city": "New York",
"country": "US"
},
"tax_rate": 0.08,
"created_at": "2026-07-13T10:30:00Z"
}
=== Desserializar ===
Pedido: ORD-001, Total: $299.99 USD
Itens: [SKU-WIDGET x3, SKU-GADGET x1]
=== Versionamento de API ===
Desserializado com campos extras: ORD-002
=== JSON Mínimo ===
Mínimo: ORD-003, Status: PENDING, Cliente: null
9. Exemplos práticos rápidos
▶ Exemplo: Serialização básica com @Serializable
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
data class Product(val sku: String, val name: String, val price: Double)
val product = Product("SKU-001", "Caneta", 2.99)
// Para serializar
val json = Json.encodeToString(product)
println("JSON: $json")
// Para deserializar
val parsed = Json.decodeFromString<Product>(json)
println("Parsed: $parsed (price=${parsed.price})")
// Pretty printing
val pretty = Json { prettyPrint = true }
println("Pretty: ${pretty.encodeToString(product)}")
// Saída:
// JSON: {"sku":"SKU-001","name":"Caneta","price":2.99}
// Parsed: Product(sku=SKU-001, name=Caneta, price=2.99)
// Pretty: {
// "sku": "SKU-001",
// "name": "Caneta",
// "price": 2.99
// }
▶ Exemplo: Campos opcionais com default
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
data class Config(
val env: String = "development",
val timeout: Int = 30,
val retries: Int = 3
)
// Serialização completa
val json = Json.encodeToString(Config("production", 60, 5))
println(json)
// Deserialização com campos ausentes usa defaults
val partial = """{"env": "staging"}"""
val config = Json.decodeFromString<Config>(partial)
println("Config: $config")
// Saída:
// {"env":"production","timeout":60,"retries":5}
// Config: Config(env=staging, timeout=30, retries=3)
▶ Exemplo: Valores null e campos opcionais
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
data class Customer(
val id: String,
val name: String,
val email: String? = null,
val phone: String? = null
)
val customer = Customer("CUST-001", "Alice", email = "alice@example.com")
val json = Json.encodeToString(customer)
println(json)
// Decodificar JSON com campos nulos
val parsed = Json.decodeFromString<Customer>(
"""{"id": "CUST-002", "name": "Bob"}"""
)
println(parsed)
println("Email: ${parsed.email ?: "(não informado)"}")
// Saída:
// {"id":"CUST-001","name":"Alice","email":"alice@example.com","phone":null}
// Customer(id=CUST-002, name=Bob, email=null, phone=null)
// Email: (não informado)
▶ Exemplo: Enums e sealed classes
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
enum class Status { PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED }
@Serializable
data class Order(
val id: String,
val status: Status
)
val order = Order("ORD-001", Status.CONFIRMED)
val json = Json.encodeToString(order)
println(json)
// Decodificar - enums case-sensitive por padrão
val parsed = Json.decodeFromString<Order>(json)
println("Status: ${parsed.status}")
// Saída:
// {"id":"ORD-001","status":"CONFIRMED"}
// Status: CONFIRMED
▶ Exemplo: Serializers customizados
import kotlinx.serialization.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.*
import java.util.Date
// Custom serializer para Date (epoch millis como Long)
object DateAsEpochSerializer : KSerializer<Date> {
override val descriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG)
override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time)
override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong())
}
@Serializable
data class Event(
val id: String,
@Serializable(with = DateAsEpochSerializer::class) val timestamp: Date
)
val event = Event("EVT-001", Date())
val json = Json.encodeToString(event)
println("Serialized: $json")
val parsed = Json.decodeFromString<Event>(json)
println("Parsed: $parsed")
// Saída:
// Serialized: {"id":"EVT-001","timestamp":1700000000000}
// Parsed: Event(id=EVT-001, timestamp=...)
▶ Exemplo: Coleções aninhadas
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
data class Cart(val customer: String, val items: List<Item>)
@Serializable
data class Item(val sku: String, val quantity: Int, val unitPrice: Double)
val cart = Cart(
customer = "Alice",
items = listOf(
Item("SKU-001", 2, 29.99),
Item("SKU-002", 1, 149.99)
)
)
val json = Json { prettyPrint = true }.encodeToString(cart)
println(json)
// Saída:
// {
// "customer": "Alice",
// "items": [
// { "sku": "SKU-001", "quantity": 2, "unitPrice": 29.99 },
// { "sku": "SKU-002", "quantity": 1, "unitPrice": 149.99 }
// ]
// }
▶ Exemplo: Polimorfismo e serialName
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.PolymorphicSerializer
@Serializable
sealed class ApiResponse<out T> {
@Serializable
@SerialName("success")
data class Success<T>(val data: T) : ApiResponse<T>()
@Serializable
@SerialName("error")
data class Error(val message: String) : ApiResponse<Nothing>()
}
// Encoded com discriminador
val success: ApiResponse<Order> = ApiResponse.Success(Order("ORD-001", Status.PENDING))
val err: ApiResponse<Order> = ApiResponse.Error("Falha de rede")
println(Json.encodeToString(success))
println(Json.encodeToString(err))
// Saída:
// {"type":"success","data":{"id":"ORD-001","status":"PENDING"}}
// {"type":"error","message":"Falha de rede"}
❓ Perguntas Frequentes
P: O kotlinx.serialization pode ser usado junto com o Jackson? R: Sim, mas não é recomendado. Os dois mecanismos de serialização são diferentes. Durante a migração, você pode usar
@JsonAliascomo ponte; para novos projetos, use kotlinx.serialization diretamente.
P: Sealed classes podem ser serializadas? R: Sim. Sealed classes
@Serializableincluem automaticamente um discriminador de tipo ("type":"SubClassName"), e a subclasse correta é escolhida durante a desserialização.
P: Como personalizar a lógica de serialização? R: Especifique um serializador personalizado com
@Serializable(with = CustomSerializer::class), ou implemente a interfaceKSerializer<T>para controlar manualmente a codificação/decodificação.
P: A serialização suporta genéricos? R: Sim, mas você precisa anotar a classe genérica com
@Serializable.decodeFromStringrequer parâmetros de tipo explícitos.
P: Por que o plugin do compilador é necessário? R: O plugin do compilador Kotlin gera automaticamente código de serializador para classes
@Serializableem tempo de compilação, evitando reflexão em tempo de execução. Esta é a base de sua performance e segurança.
P: Qual a diferença entre @SerialName e @JsonProperty? R: Funcionalmente iguais (mapeamento de nome de campo JSON), mas @SerialName é a anotação do kotlinx.serialization processada em tempo de compilação; @JsonProperty é a anotação do Jackson processada em tempo de execução via reflexão.
📖 Resumo
@Serializable+ plugin do compilador gera serializadores automaticamente — zero reflexãoJson.encodeToString/Json.decodeFromString<T>para codificação e decodificação@SerialNamemapeia nomes de campos JSON, desacoplado dos nomes de propriedades Kotlin- Valores padrão = campos opcionais;
@Requiredforça a presença do campo ignoreUnknownKeys = truehabilita compatibilidade de versionamento de API- Suporte a múltiplos formatos: JSON / ProtoBuf / CBOR / HOCON
📝 Exercícios
- Iniciante (⭐): Adicione
@SerializableaOrder, serialize para JSON e desserialize de volta. Dica:@Serializable data class Order(...) - Intermediário (⭐⭐): Use
@SerialNamepara mapear nomes de campos de Order (order_id,order_total) e configureJson { ignoreUnknownKeys = true }para evolução de API. Dica: Consulte a Seção 5 - Desafio (⭐⭐⭐): Implemente um serializador personalizado que achate os itens de
Orderem um formato JSON plano (expandindo itens para o formatoitem_1_sku,item_1_qty). Dica: ImplementeKSerializer<Order>