Serialização no Kotlin
Lissa Ferreira
Posted on August 27, 2021
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.
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:
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.
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.
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")
}
Após adicionar essas dependências, clique no elefante do Gradle no canto superior direito para atualizar as dependências:
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()
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)
- Para declarar as propriedades, você pode usar
val
ouvar
, sendoval
para propriedades que nunca irão mudar, evar
para propriedades que podem mudar; - Também é necessário colocar o nome da propriedade como
nome
, e também o tipo primitivo, comoString
ouInt
.
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)
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
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)
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)
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>{
}
- Preste atenção que o retorno da função é obrigatoriamente uma lista de planetas, logo, será
List<Planeta>
Agora podemos usar a função listOf
para 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),
)
}
- 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),
)
}
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
}
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)
}
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)]
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.*
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)
}
- 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)
}
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)
}
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}]
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)
}
As diferenças são:
- Criamos uma variável
planetasEmObjeto
, que recebe o resultado da funçãoJson.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 objetosPlaneta
; - 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)]
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
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)
}
- A função
gerarYaml
é igual ágerarJSON
, mas a diferença, é que ao invés de usar a classeJson
, é usada a classeYaml.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)
}
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)]
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.
Muito obrigada por ler ❤️🏳️⚧️ e me segue nas redes, é tudo @lissatransborda 👀
Posted on August 27, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.