Manipulando arquivos JSON com Crystal
Tags: crystal, json, programming, beginners
Published: 2023-06-06 00:53:38

Se você alguma vez já pensou em como seria trabalhar com a leitura de arquivos JSON utilizando Crystal, saiba que este é o lugar certo! Aqui veremos como podemos realizar manipulações simples de valores em JSON, trabalhando com variáveis, arrays, hashs, entre outras maneiras, manipulando nossas chaves com variáveis!

## O que é JSON
O nome JSON vem de "JavaScript Object Notation", sendo um formato compacto de padrão aberto para troca e manipulação de dados simples, criado ainda nos anos 2000 (mais especificamente em 2002) utilizando um formato de chave-valor (atributo-valor).
Ok, mas, como seria o formato de um arquivo JSON?
```json
[
	{
		"mensagem": "fala tropa"
	},
	{
		"resposta": "eai"
	}
]
```
> As chaves/atributos seriam os campos `"mensagem"` e `"resposta"`, na qual seu valor está atribuído logo após dos dois pontos (":"), sendo separadas por chaves ("{ }") e vírgula indicando o próximo atributo completo.

## Trabalhando com Crystal
Abrindo seu diretório de trabalho, crie um novo diretório para trabalharmos com este projeto, na qual vamos precisar de algumas "dependências" antes de iniciarmos:
- Tenha certeza de ter o Crystal instalado corretamente na sua máquina
- Prepare o editor de código favorito no diretório que foi criado
- Caso não tenha instalado o Crystal ainda clique [aqui](https://crystal-lang.org/install) para saber mais!
Após abrir seu diretório de trabalho, caso queira iniciar o `shards` para criação de um arquivo para controle de projeto, execute em seu terminal:
```sh
$ shards init
```
Seu arquivo `shard.yml` deverá seguir o seguinte formato:
```yml
name: json-reader

version: 0.1.0

authors:
	- João Lanjoni <guto@lanjoni.dev>

description: |
	JSON reader

targets:
	teste:
		main: src/main.cr

license: MIT
```

Agora crie um diretório chamado `src`, nele vamos colocar todo o nosso código trabalhado utilizando Crystal! Crie também um diretório chamado `content` na raiz do projeto para conter todos os nossos arquivos em formato `JSON`!
Sendo assim, basta adicionar um arquivo no formato `JSON` com o nome que desejar no diretório `content` e adicionar um arquivo `main.cr` no diretório `src`, assim, ficaremos com nossa árvore:
```tree
json-reader/ 
	├── content/ 
	│ └── index.json 
	├── src/ 
	│ └── main.cr 
	└── shard.yml
```
> Lembrando que o `shard.yml` só existe se você iniciou o shards dentro de seu projeto!

## Mãos na massa (no teclado)
Primeiramente vamos criar nosso arquivo JSON, por isso, seguindo o template passado acima crie um arquivo `.json` dentro do diretório `content`! Com ele vamos manipular os valores lá existentes!
Agora abra seu arquivo `src/main.cr` no seu editor de código favorito para podermos manipular melhor nosso projeto!
Abrindo seu arquivo primeiramente vamos importar a biblioteca `json` para trabalharmos com arquivos neste formato, assim, adicione em seu código:
```crystal
require "json"
```
> Toda biblioteca pode ser adicionada com o comando `require`!

Para informarmos qual será o arquivo a ser lido, vamos realizar uma simples passagem por argumento/opção ao executar nosso projeto! Assim, caso queira se aprofundar um pouco mais sobre a passagem de argumentos na linha de comando com Crystal clique [aqui](https://crystal-lang.org/api/1.8.2/toplevel.html). Dessa forma, vamos salvar o conteúdo do arquivo JSON em uma variável:
```crystal
content = File.read("content/#{ARGV[0]}")
```
> Para recebermos o primeiro argumento/opção utilizaremos o `ARGV[0]`, afinal, a posição 0 é a primeira no array de valores passados como opções! O `content` no começo significa que precisamos passar apenas o nome do arquivo, afinal, já será buscado um arquivo de mesmo nome no diretório especificado!

Certo, mas, existe ainda o caso do usuário não adicionar nenhuma opção, certo? Neste caso o código nem deve continuar, afinal, se um arquivo JSON não for especificado, logo, não podemos realizar a manipulação! Assim, antes, adicionamos uma simples verificação:
```crystal
if ARGV.size != 1
	puts "É necessário passar o nome do arquivo como parâmetro!"
	exit 1
end
```
> O tamanho sendo diferente de 1 demonstra que foram passados ou mais de um argumento ou nenhum, por isso, em ambos os casos, devemos rejeitar a execução!

Para realizar o `parse` de nosso JSON existem algumas formas, vamos começar com a mais simples: JSON de chaves únicas! Veja o JSON de exemplo abaixo:
```json
{
	"teste": "oi",
	"teste2": "tchau"
}
```
> Perceba que este formato não possui colchetes em seu início, indicando não ser um array!

Para realizar o `parse` vamos utilizar uma função nativa do Crystal para realizar a leitura de um JSON e depois convertê-lo em um Hash de Strings!
```crystal
hash_content = Hash(String, String).from_json(content)

puts hash_content # {"teste" => "oi", "teste2" => "tchau"}
```
> Desta forma vamos montar uma variável com nome `hash_content`, tendo em seu conteúdo um hash com chave valor de String para String, trazendo estes dados de um JSON

Logo, nosso código final será algo como:
```crystal
require "json"

# Verificação de argumentos sendo igual a 1
if ARGV.size != 1
	puts "É necessário passar o nome do arquivo como parâmetro!"
	exit 1
end

# Carregando o conteúdo do arquivo JSON
content = File.read("content/#{ARGV[0]}")  

# Transformando os dados do arquivo JSON em um Hash de String => String
hash_content = Hash(String, String).from_json(content)

# Imprimindo o conteúdo da variável
puts hash_content # {"teste" => "oi", "teste2" => "tchau"}
```

Podemos executar de duas formas:
- Utilizando `shards`:
	- ```$ shards run -- content.json```
- Utilizando o próprio `crystal`:
	- ```$ crystal run src/main.cr content.json```

Certo, mas, e se quiséssemos apenas o retorno da chave `teste`?
```crystal
puts hash_content["teste"] # oi
```

Assim, conseguimos manipular nossos dados no arquivo JSON, podendo trazer as chaves específicas para cada item!

## Arrays de JSONs
Certo, mas, a maioria dos JSONs que vamos encontrar precisam ser "parseados" utilizando formatos de Array... Como podemos fazer isso? Bom, existem duas formas específicas para realizarmos essa tarefa, bora lá?

### Primeira forma: Array de Hash
Basicamente vamos montar um array com seu tipo interno sendo hash! Mas, como isso ficaria na prática? Bom, primeiro vou repassar agora o arquivo JSON que estaremos trabalhando:
```json
[
	{
		"teste": "fala tropa"
	},
	{
		"teste2": "eai"
	}
]
```
Vamos primeiramente definir que estamos trabalhando com um array, mas, no momento de definir o tipo do array vamos trocar para `Hash(String, String)`, veja:
```crystal
json = Array(Hash(String, String)).from_json(content)

puts json # [{"teste" => "fala tropa"}, {"teste2" => "eai"}]
```
Perceba que basicamente envolvemos o formato de hash anterior no array, correto? Assim podemos trabalhar com nossos arrays de JSONs da forma que preferirmos! Mas, como eu poderia trazer apenas o conteúdo da chave "teste"? Simples, veja dois exemplos abaixo:
```crystal
puts json[0] # {"teste" => "fala tropa"}

puts json[0]["teste"] # fala tropa
```
Assim, conseguimos manipular nosso JSON!

Caso o formato de chave não seja sempre String, podemos ainda criar um `alias` para manipular os tipos passados por Hash ou então apenas adicionarmos um operador pipe ("|") demonstrando que o tipo pode variar, sendo assim seria algo como:

```crystal
Hash(String | Float64, String | Int32)
```

### Segunda forma: File.open
Podemos ainda utilizar o `File.open` para abrir um arquivo e trabalhar com seu conteúdo (na qual deixo os créditos para a [cherry](https://twitter.com/cherry_ramatis) por ter passado na [live](twitch.tv/gutolanjoni) e deixado esse detalhe importantíssimo), sem a necessidade da criação de uma variável para realizar esse trabalho (como estávamos fazendo com a variável `content`). Veja uma implementação abaixo:

```crystal
json = File.open("content/#{ARGV[0]}") do |file|
	JSON.parse(file)
end

puts json # [{"teste" => "fala tropa"}, {"teste2" => "eai"}]

puts json[0] # {"teste" => "fala tropa"}

puts json[0]["teste"] # fala tropa
```
> Neste exemplo realizamos um simples "parse" de valores e atualizamos na variável de nome `json`, podendo trabalhar com os valores no mesmo formato que anteriormente!

Assim, nosso projeto completo finaliza na segunda forma! Veja como ficou nosso código finalizado:
```crystal
require "json"

if ARGV.size != 1
	puts "É necessário passar o nome do arquivo como parâmetro!"
	exit 1
end

json = File.open("content/#{ARGV[0]}") do |file|
	JSON.parse(file)
end

puts json # [{"teste" => "fala tropa"}, {"teste2" => "eai"}]
```

## Finalização
Com este guia e pequeno artigo você aprendeu a como manipular arquivos JSON e dados vindo de outros arquivos utilizando Crystal, baseando-se no modelo chave-valor que é oferecido, podendo criar comandos, leituras específicas, entre outros tipos de projetos! Para acessar o código desenvolvido neste artigo basta clicar [aqui](https://github.com/lanjoni/json-reader-crystal)! 
Nos vemos na próxima, até mais! Cristalize ainda mais o seu dia! 💎 🖤