DEV Community

Cover image for Serialização no Kotlin
Ederson Ferreira for Kotlinautas

Posted on

Serialização no Kotlin

Kotlinautas

Esse conteúdo é oferecido e distribuído pela comunidade Kotlinautas, uma comunidade brasileira que busca oferecer conteúdo gratuito sobre a linguagem Kotlin em um espaço plural.

capa Kotlinautas

O quê é Serialização?

Serialização é a transformação de estruturas de dados em algo que pode ser armazenado, e depois re-transformado em estruturas de dados.

Para exemplificar, vamos supor que você teve um sonho. Caso você não anote esse sonho de nenhuma maneira, você irá esquecer desse sonho. Mas se você anotar detalhadamente o sonho, á qualquer momento você pode lembrar novamente do sonho lendo as suas anotações. O sonho é como se fosse uma estrutura de dados (Uma lista, um objeto,etc.), e as anotações são o resultado da serialização do sonho.

Casos de uso

A serialização pode ser usada para comunicação entre cliente e servidor, transferindo dados de maneira mais simples, como por exemplo, um frontend fazendo o processo de Serialização para transferir um objeto Javascript para um backend feito em Kotlin. Também é útil para guardar dados, como por exemplo, um bot guardando nomes e mensagens de diversas pessoas em um arquivo.

Para qualquer caso em que é necessário transferir dados de um sistema para outro, ou guardar dados, uma serialização é um grande recurso á ser contado.

O quê é JSON, XML, Yaml,etc?

JSON, XML, Yaml,etc. são maneiras de guardar dados após uma serialização. É como se fosse a língua que iremos anotar o sonho, e se iremos anotar o sonho em forma de resumo, ou se iremos anotar o sonho em passos cronológicos do quê aconteceu no sonho.

JSON é um dos mais usados, pois uma aplicação de frontend web é geralmente feita com Javascript. Javascript tem como uma funcionalidade padrão a transformação de JSON em objetos Javascript (JSON significa Notação de Objeto Javascript). Logo é mais fácil fazer essa transferência usando JSON em muitos dos casos.

XML e Yaml são duas maneiras de guardar dados mais utilizadas em arquivos de configuração geralmente, mas atualmente, é mais usado o Yaml pela sua sintáxe mais limpa e simples.

O quê vamos criar?

Vamos supor que temos um telecópio chamado Telesnauta, esse telescópio fotografou e capturou dados sobre os planetas do sistema solar, esses dados sendo o nome, distância ao sol em kilômetros, massa do planeta comparada á terra, e se o planeta é sólido ou não. Esses dados sendo apresentados em forma de tabela seriam assim:

Planeta Distância do Sol em kilômetros Massa comparada á terra Sólido
Mercúrio 57 910 000 0.1 true
Vênus 108 208 930 0.9 true
Terra 149 597 870 1.0 true
Marte 227 936 640 0.1 true
Júpiter 778 412 010 318.0 false
Saturno 1 426 725 400 95.0 false
Urano 2 870 972 200 15.0 false
Netuno 4 498 252 900 17.0 false

Esses dados podem ser representados de diversas maneiras, mas no caso, vamos transformar esses dados em objetos no Kotlin, e transformar esses objetos em JSON e Yaml, usando uma biblioteca de serialização para Kotlin. Com isso, teremos um exemplo mais prático de como serializar esses dados.

Criação do projeto

Vá no seu intelliJ, e clique no botão New Project para criar um novo projeto:

Botão new project do intelliJ

Após isso, na interface de configurações do Gradle deverão ficar assim, habilitando o Kotlin DSL build script, e também habilitar a opção Kotlin/JVM. Opicionalmente você pode remover a opção Java, pois não iremos usar Java nesse projeto.

Configurações do Gradle

Agora escolha um nome para nomear o projeto, pode ser qualquer nome que você quiser. Caso não tenha nenhuma ideia, pode ser algo como serializacao por exemplo.

Nomeação do projeto

kotlinx.serialization

kotlinx.serialization é a biblioteca oficial do Kotlin para fazer serialização, oficialmente suportando:

  • JSON
  • Protobuf
  • CBOR
  • Hocon
  • Properties
  • E mais, instalando outros formatos adicionados pela comunidade

Será essa a biblioteca que vamos usar para fazer as serializações, tanto por essa ser a biblioteca oficial, quanto por permitir o uso de diversos arquivos finais, como JSON, Hocon, Properties,etc.

Caso você queria saber mais, acesse esse repositório do Github para ter mais informações.

Instalação

Vá ao arquivo src/main/kotlin/build.kts e vamos adicionar duas dependências, uma sendo um suporte oficial do kotlinx.serialization, sendo o JSON, e outro um suporte da comunidade, sendo o Yaml. Vá á seção dependencies e deixe essa seção dessa maneira:

dependencies {
    implementation(kotlin("stdlib"))
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2")
    implementation("com.charleskorn.kaml:kaml:0.35.2")
}
Enter fullscreen mode Exit fullscreen mode

Após adicionar essas dependências, clique no elefante do Gradle no canto superior direito para atualizar as dependências:

Elefante do gradle no canto superior direito

Agora crie um arquivo chamado main.kt dentro de src/main/kotlin/. Iremos usar esse arquivo para armazenar todo o código do nosso programa.

como funciona a kotlinx.serialization?

A biblioteca kotlinx.serialization se baseia em transformar formatos de serialização (JSON, Yaml, XML,etc) em classes do Kotlin. Essas classes terão propriedades, e cada propriedade da classe, irá representar um dado de dentro da mensagem original (Um texto em JSON por exemplo). Essas classes serão Data Classes, classes que apenas servem para armazenar dados. Iremos ver isso na prática mais á frente.

Como posso criar essas classes no Kotlin?

Podemos criar da seguinte maneira:

data class NomeDaClasse()
Enter fullscreen mode Exit fullscreen mode

Após isso, dentro das () iremos inserir as propriedades da classe. Se quisermos inserir um nome e uma idade por exemplo, fariamos dessa maneira:

data class NomeDaClasse(val nome: String, val idade: Int)
Enter fullscreen mode Exit fullscreen mode
  • Para declarar as propriedades, você pode usar val ou var, sendo val para propriedades que nunca irão mudar, e var para propriedades que podem mudar;
  • Também é necessário colocar o nome da propriedade como nome, e também o tipo primitivo, como String ou Int.

Criando Classe: Planeta

Como nosso sistema terá de armazenar dados de planetas do sistema solar, a classe que precisaremos criar será uma classe Planeta, que irá representar um planeta em nossa aplicação.

Como vimos na tabela, um planeta precisará ter:

  • Nome: String, pois será um texto
  • A distância ao sol: Long, pois será um número
  • A massa do planeta em relação á terra: Double, Pois será um número decimal, como 0.1, 1.25,etc.
  • Se o planeta é sólido ou não: Boolean, pois será uma variável ou verdadeira, ou falsa.

Com isso em mente, a nossa classe ficará assim:

data class Planeta(val nome: String, val distância: Long, val massa: Double, val sólido: Boolean)
Enter fullscreen mode Exit fullscreen mode

Agora, precisaremos importar a kotlinx.serialization, especificadamente a classe de Serializable, com essa classe poderemos transformar uma Data Class comum, em uma Data Class que pode ser serializada.

Importe a classe Serializable dessa maneira:

import kotlinx.serialization.Serializable
Enter fullscreen mode Exit fullscreen mode

Agora, adicione acima da Data Class Planeta, um @Serializable que deixa explícito que essa classe pode ser serializada:

@Seriazable
data class Planeta(val nome: String, val distância: Long, val massa: Double, val sólido: Boolean)
Enter fullscreen mode Exit fullscreen mode

Dessa maneira, nosso arquivo ficará assim:

import kotlinx.serialization.Serializable

@Seriazable
data class Planeta(val nome: String, val distância: Long, val massa: Double, val sólido: Boolean)
Enter fullscreen mode Exit fullscreen mode

Agora já temos uma classe que representa um planeta, podemos usar essa classe para criar instâncias de planetas.

Gerando planetas

Vamos criar uma função que irá gerar todos os planetas do sistema solar, criando uma lista com todos esses dados, e retornando essa lista.

Primeiro, vamos declarar a função:

fun gerarPlanetas(): List<Planeta>{

}
Enter fullscreen mode Exit fullscreen mode
  • Preste atenção que o retorno da função é obrigatoriamente uma lista de planetas, logo, será List<Planeta>

Agora podemos usar a função listOfpara criar uma lista dos planetas:

fun gerarPlanetas(): List<Planeta>{
    val listaDePlanetas = listOf(
        Planeta("Mercúrio", 57_910_000, 0.1, true),
        Planeta("Vênus", 108_208_930, 0.9, true),
        Planeta("Terra", 149_597_870, 1.0, true),
        Planeta("Marte", 227_936_640, 0.1, true),
        Planeta("Júpiter", 778_412_010, 318.0, false),
        Planeta("Saturno", 1_426_725_400, 95.0, false),
        Planeta("Urano", 2_870_972_200, 15.0, false),
        Planeta("Netuno", 4_498_252_900, 17.0, false),
    )
}
Enter fullscreen mode Exit fullscreen mode
  • Estamos colocando os parâmetros na mesma ordem que declaramos, caso você queira, também é possível de colocar os argumentos nomeados. Dessa maneira a ordem dos parâmetros não terá diferença. Caso você prefira desse jeito, o código ficará assim:
fun gerarPlanetas(): List<Planeta>{
    val listaDePlanetas = listOf(
        Planeta(nome = "Mercúrio", distância = 57_910_000, sólido = true, massa = 0.1),
        Planeta(nome = "Vênus", distância = 108_208_930, massa = 0.9, sólido = true),
        Planeta(nome = "Terra", distância = 149_597_870, massa = 1.0, sólido = true),
        Planeta(nome = "Marte", distância = 227_936_640, massa = 0.1, sólido = true),
        Planeta(nome = "Júpiter", distância = 778_412_010, massa = 318.0, sólido = false),
        Planeta(nome = "Saturno", distância = 1_426_725_400, massa = 95.0, sólido = false),
        Planeta(nome = "Urano", distância = 2_870_972_200, massa = 15.0, sólido = false),
        Planeta(nome = "Netuno", distância = 4_498_252_900, massa = 17.0, sólido = false),
    )
}
Enter fullscreen mode Exit fullscreen mode

E ao final iremos retornar a lista:

fun gerarPlanetas(): List<Planeta>{
    val listaDePlanetas = listOf(
        Planeta("Mercúrio", 57_910_000, 0.1, true),
        Planeta("Vênus", 108_208_930, 0.9, true),
        Planeta("Terra", 149_597_870, 1.0, true),
        Planeta("Marte", 227_936_640, 0.1, true),
        Planeta("Júpiter", 778_412_010, 318.0, false),
        Planeta("Saturno", 1_426_725_400, 95.0, false),
        Planeta("Urano", 2_870_972_200, 15.0, false),
        Planeta("Netuno", 4_498_252_900, 17.0, false),
    )

  return listaDePlanetas
}
Enter fullscreen mode Exit fullscreen mode

Agora podemos criar a nossa função main, que por enquanto apenas irá ter uma variável chamada planetas, que irá guardar a lista retornada da função gerarPlanetas, e mostrar essa lista na tela:

fun main(){
    val planetas = gerarPlanetas()
    println(planetas)
}
Enter fullscreen mode Exit fullscreen mode

Agora, caso você tente rodar o código atual, a lista de planetas será mostrada na tela:

[Planeta(nome=Mercúrio, distância=57910000, massa=0.1, sólido=true), Planeta(nome=Vênus, distância=108208930, massa=0.9, sólido=true), Planeta(nome=Terra, distância=149597870, massa=1.0, sólido=true), Planeta(nome=Marte, distância=227936640, massa=0.1, sólido=true), Planeta(nome=Júpiter, distância=778412010, massa=318.0, sólido=false), Planeta(nome=Saturno, distância=1426725400, massa=95.0, sólido=false), Planeta(nome=Urano, distância=2870972200, massa=15.0, sólido=false), Planeta(nome=Netuno, distância=4498252900, massa=17.0, sólido=false)]
Enter fullscreen mode Exit fullscreen mode

Pronto, agora temos os dados dos planetas em forma de objetos do Kotlin.

Serializando objetos Kotlin em JSON

Agora, vamos começar a trabalhar com a kotlinx.serialization para serializar esses objetos Planeta em JSON.

Primeiro, importe a classe Json da kotlinx.serialization. Será ela que vamos usar para usar JSON:

import kotlinx.serialization.json.Json
import kotlinx.serialization.*
Enter fullscreen mode Exit fullscreen mode

Dentro da classe Json, temos duas funções bem importantes:

  • Json.encodeToString - Tranforma um objeto (como nossa lista de planetas) em JSON
  • Json.decodeFromString<Tipo> Transforma uma string (texto) em um objeto do Kotlin

Com isso em mente, vamos criar uma função chamada gerarJSON, que irá receer a lista de planestas, transformar em JSON e mostrar esse JSON na tela, dessa maneira:

fun gerarJSON(planetas: List<Planeta>) {
    val planetasEmJson = Json.encodeToString(planetas)
    println(planetasEmJson)
}
Enter fullscreen mode Exit fullscreen mode
  • O único argumento para a função é a lista de planetas como objeto;
  • Transformamos a lista em texto com Json.encodeToString;
  • Mostramos na tela o texto em JSON;

Agora, vamos á nossa função main e vamos chamar a função gerarJSON:

fun main(){
    val planetas = gerarPlanetas()
    println("JSON:")
    gerarJSON(planetas)
}
Enter fullscreen mode Exit fullscreen mode

Dessa maneira, o arquivo final ficará assim:

@Serializable
data class Planeta(val nome: String, val distância: Long, val massa: Double, val sólido: Boolean)

fun main(){
    val planetas = gerarPlanetas()
    println("JSON:")
    gerarJSON(planetas)
}

fun gerarJSON(planetas: List<Planeta>) {
    val planetasEmJson = Json.encodeToString(planetas)
    println(planetasEmJson)
}
Enter fullscreen mode Exit fullscreen mode

Rodando o código, o output será:

JSON:
[{"nome":"Mercúrio","distância":57910000,"massa":0.1,"sólido":true},{"nome":"Vênus","distância":108208930,"massa":0.9,"sólido":true},{"nome":"Terra","distância":149597870,"massa":1.0,"sólido":true},{"nome":"Marte","distância":227936640,"massa":0.1,"sólido":true},{"nome":"Júpiter","distância":778412010,"massa":318.0,"sólido":false},{"nome":"Saturno","distância":1426725400,"massa":95.0,"sólido":false},{"nome":"Urano","distância":2870972200,"massa":15.0,"sólido":false},{"nome":"Netuno","distância":4498252900,"massa":17.0,"sólido":false}]
Enter fullscreen mode Exit fullscreen mode

Agora vamos mudar o código da gerarJSON para tanto gerar o texto em JSON, quanto para pegar esse texto em JSON e transformar em objetos do Kotlin:

fun gerarJSON(planetas: List<Planeta>) {
    val planetasEmJson = Json.encodeToString(planetas)
    val planetasEmObjeto = Json.decodeFromString<List<Planeta>>(planetasEmJson)
    println(planetasEmJson)
    println(planetasEmObjeto)
}
Enter fullscreen mode Exit fullscreen mode

As diferenças são:

  • Criamos uma variável planetasEmObjeto, que recebe o resultado da função Json.decodeFromString;
  • Essa função Json.decodeFromString precisa receber um tipo primitivo. No caso, o tipo primitivo é List<Planeta>, que representa uma lista de várias instâncias do objeto planeta;
  • Depois, enviamos para Json.decodeFromString o texto em JSON como argumento, e ao final, temos uma lista de objetos Planeta;
  • Mostramos na tela esses objetos;

Agora, o output estará assim:

JSON:
[{"nome":"Mercúrio","distância":57910000,"massa":0.1,"sólido":true},{"nome":"Vênus","distância":108208930,"massa":0.9,"sólido":true},{"nome":"Terra","distância":149597870,"massa":1.0,"sólido":true},{"nome":"Marte","distância":227936640,"massa":0.1,"sólido":true},{"nome":"Júpiter","distância":778412010,"massa":318.0,"sólido":false},{"nome":"Saturno","distância":1426725400,"massa":95.0,"sólido":false},{"nome":"Urano","distância":2870972200,"massa":15.0,"sólido":false},{"nome":"Netuno","distância":4498252900,"massa":17.0,"sólido":false}]
[Planeta(nome=Mercúrio, distância=57910000, massa=0.1, sólido=true), Planeta(nome=Vênus, distância=108208930, massa=0.9, sólido=true), Planeta(nome=Terra, distância=149597870, massa=1.0, sólido=true), Planeta(nome=Marte, distância=227936640, massa=0.1, sólido=true), Planeta(nome=Júpiter, distância=778412010, massa=318.0, sólido=false), Planeta(nome=Saturno, distância=1426725400, massa=95.0, sólido=false), Planeta(nome=Urano, distância=2870972200, massa=15.0, sólido=false), Planeta(nome=Netuno, distância=4498252900, massa=17.0, sólido=false)]
Enter fullscreen mode Exit fullscreen mode

Como você pode ver, mostramos o texto em JSON e o texto em JSON tranformado em objeto do Kotlin novamente.

Serializando objetos Kotlin em Yaml

Primeiro, vamos importar a classe Yaml, da biblioteca com.charleskorn.kaml:kaml:0.35.2, que é uma biblioteca de serialização em Kotlin com suporte pela comunidade, e não de maneira oficial como é com a biblioteca de JSON:

import com.charleskorn.kaml.Yaml
Enter fullscreen mode Exit fullscreen mode

Agora vamos criar uma função chamada gerarYaml para faze a mesma coisa que a gerarJSON, mas com Yaml:

fun gerarYaml(planetas: List<Planeta>) {
    val planetasEmYaml = Yaml.default.encodeToString(planetas)
    val planetasEmObjeto = Yaml.default.decodeFromString<List<Planeta>>(planetasEmYaml)
    println(planetasEmYaml)
    println(planetasEmObjeto)
}
Enter fullscreen mode Exit fullscreen mode
  • A função gerarYaml é igual á gerarJSON, mas a diferença, é que ao invés de usar a classe Json, é usada a classe Yaml.default. Tirando isso, todo o código é igual.

Agora vamos mudar a main para usar a função gerarYaml também:

fun main(){
    val planetas = gerarPlanetas()
    println("JSON:")
    gerarJSON(planetas)

    println("Yaml:")
    gerarYaml(planetas)
}
Enter fullscreen mode Exit fullscreen mode

Ao final, o output do nosso código ficou assim:

JSON:
[{"nome":"Mercúrio","distância":57910000,"massa":0.1,"sólido":true},{"nome":"Vênus","distância":108208930,"massa":0.9,"sólido":true},{"nome":"Terra","distância":149597870,"massa":1.0,"sólido":true},{"nome":"Marte","distância":227936640,"massa":0.1,"sólido":true},{"nome":"Júpiter","distância":778412010,"massa":318.0,"sólido":false},{"nome":"Saturno","distância":1426725400,"massa":95.0,"sólido":false},{"nome":"Urano","distância":2870972200,"massa":15.0,"sólido":false},{"nome":"Netuno","distância":4498252900,"massa":17.0,"sólido":false}]
[Planeta(nome=Mercúrio, distância=57910000, massa=0.1, sólido=true), Planeta(nome=Vênus, distância=108208930, massa=0.9, sólido=true), Planeta(nome=Terra, distância=149597870, massa=1.0, sólido=true), Planeta(nome=Marte, distância=227936640, massa=0.1, sólido=true), Planeta(nome=Júpiter, distância=778412010, massa=318.0, sólido=false), Planeta(nome=Saturno, distância=1426725400, massa=95.0, sólido=false), Planeta(nome=Urano, distância=2870972200, massa=15.0, sólido=false), Planeta(nome=Netuno, distância=4498252900, massa=17.0, sólido=false)]
Yaml:
- nome: "Mercúrio"
  distância: 57910000
  massa: 0.1
  sólido: true
- nome: "Vênus"
  distância: 108208930
  massa: 0.9
  sólido: true
- nome: "Terra"
  distância: 149597870
  massa: 1.0
  sólido: true
- nome: "Marte"
  distância: 227936640
  massa: 0.1
  sólido: true
- nome: "Júpiter"
  distância: 778412010
  massa: 318.0
  sólido: false
- nome: "Saturno"
  distância: 1426725400
  massa: 95.0
  sólido: false
- nome: "Urano"
  distância: 2870972200
  massa: 15.0
  sólido: false
- nome: "Netuno"
  distância: 4498252900
  massa: 17.0
  sólido: false
[Planeta(nome=Mercúrio, distância=57910000, massa=0.1, sólido=true), Planeta(nome=Vênus, distância=108208930, massa=0.9, sólido=true), Planeta(nome=Terra, distância=149597870, massa=1.0, sólido=true), Planeta(nome=Marte, distância=227936640, massa=0.1, sólido=true), Planeta(nome=Júpiter, distância=778412010, massa=318.0, sólido=false), Planeta(nome=Saturno, distância=1426725400, massa=95.0, sólido=false), Planeta(nome=Urano, distância=2870972200, massa=15.0, sólido=false), Planeta(nome=Netuno, distância=4498252900, massa=17.0, sólido=false)]
Enter fullscreen mode Exit fullscreen mode

Pronto! Agora temos uma lista de objetos do Kotlin sendo representada em JSON e em Yaml.

Finalização

Agora você já tem uma boa idéia de como fazer serialização no Kotlin, agora, explore mais formatos de serialização que podem ser usados nessa lista do repositório oficial da kotlinx.serialization, e outras maneiras de usar a serialização.

Obrigado por ler 💙

Discussion (2)

Collapse
tporto profile image
Thiago Porto

Muito bom!

Collapse
aristotelesbr2 profile image
Aristóteles Coutinho

Parabéns pelo post! Sensacional! 👏🏻👏🏻👏🏻