---
title: "Kotlin Serialization Avançada: Polimorfismo e Serializers Customizados | Kotlin Brasil"
url: "https://kotlin.dev.br/blog/kotlin-serialization-avancada-polimorfismo-2026/"
markdown_url: "https://kotlin.dev.br/blog/kotlin-serialization-avancada-polimorfismo-2026.MD"
description: "Domine serialização avançada em Kotlin: polimorfismo com sealed classes, serializers customizados, contextual serialization e padrões para APIs complexas."
date: "2026-04-28"
author: "Karina Melo"
---

# Kotlin Serialization Avançada: Polimorfismo e Serializers Customizados | Kotlin Brasil

Domine serialização avançada em Kotlin: polimorfismo com sealed classes, serializers customizados, contextual serialization e padrões para APIs complexas.


A `kotlinx.serialization` é a biblioteca oficial do Kotlin para serialização e desserialização de dados, e vai muito além do básico `@Serializable` em data classes. Quando você trabalha com APIs que retornam tipos variados, precisa lidar com formatos legados ou quer otimizar payloads, os recursos avançados da biblioteca fazem toda a diferença.

Neste artigo, vamos explorar **polimorfismo com sealed classes**, **serializers customizados**, **serialização contextual** e padrões avançados que resolvem problemas reais em projetos Kotlin. Se você já domina o básico, este guia vai elevar seu nível.

## Polimorfismo com sealed classes

O cenário mais comum de serialização polimórfica acontece quando uma API retorna diferentes tipos de objetos em uma mesma lista ou campo. Considere um sistema de notificações:

```kotlin
import kotlinx.serialization.*
import kotlinx.serialization.json.*

@Serializable
sealed class Notificacao {
    abstract val id: String
    abstract val timestamp: Long

    @Serializable
    @SerialName("email")
    data class Email(
        override val id: String,
        override val timestamp: Long,
        val destinatario: String,
        val assunto: String,
        val corpo: String
    ) : Notificacao()

    @Serializable
    @SerialName("push")
    data class Push(
        override val id: String,
        override val timestamp: Long,
        val titulo: String,
        val mensagem: String,
        val icone: String? = null
    ) : Notificacao()

    @Serializable
    @SerialName("sms")
    data class Sms(
        override val id: String,
        override val timestamp: Long,
        val telefone: String,
        val texto: String
    ) : Notificacao()
}
```

Com sealed classes, o compilador conhece todos os subtipos em tempo de compilação, e a serialização funciona automaticamente:

```kotlin
val json = Json { prettyPrint = true }

val notificacoes: List<Notificacao> = listOf(
    Notificacao.Email(
        id = "1",
        timestamp = System.currentTimeMillis(),
        destinatario = "dev@kotlin.dev.br",
        assunto = "Deploy concluído",
        corpo = "O deploy da v2.5 foi finalizado com sucesso."
    ),
    Notificacao.Push(
        id = "2",
        timestamp = System.currentTimeMillis(),
        titulo = "Nova versão",
        mensagem = "Kotlin 2.4.0 disponível!"
    )
)

val texto = json.encodeToString(notificacoes)
println(texto)
```

O JSON gerado inclui automaticamente o campo discriminador `type`:

```json
[
  {
    "type": "email",
    "id": "1",
    "timestamp": 1745856000000,
    "destinatario": "dev@kotlin.dev.br",
    "assunto": "Deploy concluído",
    "corpo": "O deploy da v2.5 foi finalizado com sucesso."
  },
  {
    "type": "push",
    "id": "2",
    "timestamp": 1745856000000,
    "titulo": "Nova versão",
    "mensagem": "Kotlin 2.4.0 disponível!"
  }
]
```

O `@SerialName` define o valor do discriminador — sem ele, o nome completo da classe é usado, o que raramente é desejável em APIs públicas.

## Serializers customizados com KSerializer

Quando o formato do JSON não corresponde à estrutura da sua classe Kotlin, você precisa de um serializer customizado. Um caso clássico: APIs que representam datas como strings em formato brasileiro:

```kotlin
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import java.time.LocalDate
import java.time.format.DateTimeFormatter

object LocalDateBrSerializer : KSerializer<LocalDate> {
    private val formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy")

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

    override fun serialize(encoder: Encoder, value: LocalDate) {
        encoder.encodeString(value.format(formatter))
    }

    override fun deserialize(decoder: Decoder): LocalDate {
        return LocalDate.parse(decoder.decodeString(), formatter)
    }
}

@Serializable
data class Evento(
    val nome: String,
    @Serializable(with = LocalDateBrSerializer::class)
    val data: LocalDate,
    val local: String
)
```

Agora, ao serializar um `Evento`, a data aparece no formato `28/04/2026` em vez do ISO padrão. Esse padrão é especialmente útil para integrar com APIs legadas ou serviços que usam formatos não padronizados.

## JsonContentPolymorphicSerializer para APIs sem discriminador

Nem toda API inclui um campo `type` para indicar o tipo do objeto. Muitas APIs retornam objetos onde o tipo é inferido pela presença ou ausência de campos específicos. O `JsonContentPolymorphicSerializer` resolve isso:

```kotlin
@Serializable(with = PagamentoSerializer::class)
sealed class Pagamento {
    abstract val valor: Double

    @Serializable
    data class CartaoCredito(
        override val valor: Double,
        val bandeira: String,
        val ultimos4Digitos: String,
        val parcelas: Int = 1
    ) : Pagamento()

    @Serializable
    data class Pix(
        override val valor: Double,
        val chave: String,
        val txId: String
    ) : Pagamento()

    @Serializable
    data class Boleto(
        override val valor: Double,
        val codigoBarras: String,
        val vencimento: String
    ) : Pagamento()
}

object PagamentoSerializer : JsonContentPolymorphicSerializer<Pagamento>(
    Pagamento::class
) {
    override fun selectDeserializer(
        element: JsonElement
    ): DeserializationStrategy<Pagamento> {
        val jsonObject = element.jsonObject
        return when {
            "bandeira" in jsonObject -> Pagamento.CartaoCredito.serializer()
            "chave" in jsonObject -> Pagamento.Pix.serializer()
            "codigoBarras" in jsonObject -> Pagamento.Boleto.serializer()
            else -> throw SerializationException(
                "Tipo de pagamento desconhecido: ${jsonObject.keys}"
            )
        }
    }
}
```

O serializer examina o conteúdo JSON para decidir qual tipo instanciar. Essa abordagem funciona perfeitamente com APIs de terceiros onde você não tem controle sobre o formato de resposta.

## Serialização contextual

A serialização contextual permite registrar serializers em runtime, separando a lógica de serialização da definição da classe. Isso é útil quando uma mesma classe precisa ser serializada de formas diferentes dependendo do contexto:

```kotlin
import kotlinx.serialization.modules.*

@Serializable
data class Relatorio(
    val titulo: String,
    @Contextual
    val geradoEm: LocalDate,
    val dados: Map<String, Int>
)

// Módulo para formato brasileiro
val moduloBrasileiro = SerializersModule {
    contextual(LocalDateBrSerializer)
}

// Módulo para formato ISO
val moduloISO = SerializersModule {
    contextual(LocalDateISOSerializer)
}

// JSON configurado para o contexto brasileiro
val jsonBr = Json {
    serializersModule = moduloBrasileiro
    prettyPrint = true
}

// JSON configurado para APIs internacionais
val jsonISO = Json {
    serializersModule = moduloISO
}

fun main() {
    val relatorio = Relatorio(
        titulo = "Vendas Q1",
        geradoEm = LocalDate.of(2026, 4, 28),
        dados = mapOf("janeiro" to 1500, "fevereiro" to 2300, "marco" to 1800)
    )

    // Mesmo objeto, formatos diferentes
    println(jsonBr.encodeToString(relatorio))
    // geradoEm: "28/04/2026"

    println(jsonISO.encodeToString(relatorio))
    // geradoEm: "2026-04-28"
}
```

Esse padrão é poderoso em aplicações que servem múltiplos clientes — um frontend brasileiro pode receber datas em formato local, enquanto uma API parceira recebe no formato ISO.

## Polimorfismo aberto com SerializersModule

Quando você não pode usar sealed classes (por exemplo, quando subtipos são definidos em módulos diferentes), o polimorfismo aberto com registro explícito é a solução:

```kotlin
@Serializable
abstract class Componente {
    abstract val id: String
}

// Módulo A
@Serializable
@SerialName("botao")
data class Botao(
    override val id: String,
    val texto: String,
    val acao: String
) : Componente()

// Módulo B
@Serializable
@SerialName("campo_texto")
data class CampoTexto(
    override val id: String,
    val placeholder: String,
    val maxLength: Int = 255
) : Componente()

// Registro de todos os subtipos
val componentesModule = SerializersModule {
    polymorphic(Componente::class) {
        subclass(Botao::class, Botao.serializer())
        subclass(CampoTexto::class, CampoTexto.serializer())
    }
}

val json = Json {
    serializersModule = componentesModule
    classDiscriminator = "componente_tipo"
}
```

Note o uso de `classDiscriminator` para customizar o nome do campo discriminador — útil quando o nome padrão `type` conflita com campos do seu modelo.

## Tratamento de campos desconhecidos e defaults

Em APIs reais, é comum receber campos que não existem no seu modelo ou lidar com campos opcionais. A configuração correta do `Json` evita crashes em produção:

```kotlin
val jsonResilient = Json {
    // Ignora campos no JSON que não existem na classe
    ignoreUnknownKeys = true
    // Usa valores default para campos ausentes no JSON
    encodeDefaults = false
    // Permite valores nulos para campos não-nullable com default
    coerceInputValues = true
    // Aceita JSON malformado (aspas simples, trailing commas)
    isLenient = true
}

@Serializable
data class UsuarioAPI(
    val id: Long,
    val nome: String,
    val email: String,
    val premium: Boolean = false,  // Default se ausente
    val avatar: String? = null     // Nullable com default
)
```

Com `ignoreUnknownKeys = true`, o serializer não lança exceção quando a API adiciona novos campos que seu modelo ainda não contempla — essencial para manter compatibilidade com APIs que evoluem sem versionamento.

## Performance e boas práticas

Algumas dicas para otimizar a serialização em projetos de produção:

```kotlin
// Crie uma única instância de Json e reutilize
val appJson = Json {
    ignoreUnknownKeys = true
    encodeDefaults = false
    serializersModule = SerializersModule {
        contextual(LocalDateBrSerializer)
        polymorphic(Notificacao::class) {
            subclass(Notificacao.Email::class)
            subclass(Notificacao.Push::class)
            subclass(Notificacao.Sms::class)
        }
    }
}

// Evite criar instâncias de Json dentro de loops ou funções chamadas frequentemente
// A instância de Json contém cache interno de serializers
```

Se você usa [Ktor para APIs](/blog/ktor-criando-apis-kotlin/), a `kotlinx.serialization` é a escolha nativa e se integra perfeitamente com o content negotiation. Para quem trabalha com [Spring Boot e Kotlin](/blog/kotlin-spring-boot/), o suporte também funciona bem como alternativa ao Jackson.

## Conclusão

A `kotlinx.serialization` vai muito além do básico `@Serializable`. Com polimorfismo — via [sealed classes](/blog/sealed-classes-kotlin/) ou registro aberto — serializers customizados e serialização contextual, você consegue modelar praticamente qualquer formato de dados que encontrar em produção.

As técnicas deste artigo são especialmente valiosas quando você integra com APIs externas que fogem dos padrões, trabalha com múltiplos formatos de saída ou precisa de performance otimizada. Se você usa [coroutines e Flow](/blog/kotlin-flow/) para consumir dados reativamente, a serialização eficiente é peça fundamental do pipeline.

Outra linguagem que se destaca em serialização type-safe é <a href="https://rustlang.com.br" target="_blank" onclick="umami.track('portfolio-site-click', {destination: 'rustlang.com.br'})">Rust com serde</a> — vale comparar as abordagens se você trabalha com múltiplas linguagens. Para quem vem de <a href="https://python.dev.br" target="_blank" onclick="umami.track('portfolio-site-click', {destination: 'python.dev.br'})">Python</a>, a kotlinx.serialization oferece segurança de tipos em tempo de compilação que Pydantic e dataclasses só validam em runtime.
