Serialização no Kotlin
Tags: kotlin, backend, braziliandevs, serialization
Published: 2021-08-27 04:04:07

## Kotlinautas

Esse conteúdo é oferecido e distribuído pela comunidade [Kotlinautas](https://twitter.com/kotlinautas/), uma comunidade brasileira que busca oferecer conteúdo gratuito sobre a linguagem Kotlin em um espaço plural.

![capa Kotlinautas](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jreawpvk6whxigcpmctf.png)

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dvjraq4cccac9wdbm2w4.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w2vdv0qcl5nqksdddsoy.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/grk0ae6x34v0h601si5j.png)

## 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](https://github.com/Kotlin/kotlinx.serialization) 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:

```kotlin
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:

![Elefante do gradle no canto superior direito](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/la68z1jte3tsph762hh3.png)

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:

```kotlin
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:

```kotlin
data class NomeDaClasse(val nome: String, val idade: Int)
```

- 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:

```kotlin
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:

```kotlin
import kotlinx.serialization.Serializable
```

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

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

Dessa maneira, nosso arquivo ficará assim:

```kotlin
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:

```kotlin
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:

```kotlin
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:

```kotlin
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:

```kotlin
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:

```kotlin
fun main(){
	val planetas = gerarPlanetas()
	println(planetas)
}
```

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

```kotlin
[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:

```kotlin
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:

```kotlin
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`:

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

Dessa maneira, o arquivo final ficará assim:

```kotlin
@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:

```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çã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:

```kotlin
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:

```kotlin
import com.charleskorn.kaml.Yaml
```

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

```kotlin
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 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:

```kotlin
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](https://github.com/Kotlin/kotlinx.serialization/blob/master/formats/README.md), e outras maneiras de usar a serialização.

Muito obrigada por ler ❤️🏳️‍⚧️ e me segue nas redes, é tudo [@lissatransborda](https://twitter.com/lissatransborda) 👀