Manipulating JSON files with Crystal
Tags: crystal, json, programming, beginners
Published: 2023-06-06 01:43:34

If you ever thought about what it would be like to work with reading JSON files using Crystal, know that this is the right place! Here we will see how we can perform simple manipulations of values in JSON, working with variables, arrays, hashs, among other ways, manipulating our keys with variables!

## What is JSON
The name JSON comes from "JavaScript Object Notation", being a compact open standard format for exchanging and manipulating simple data, created in the 2000s (more specifically in 2002) using a key-value format (attribute-value).
Okay, but what would a JSON file format look like?
```json
[
	{
		"message": "talk troop"
	},
	{
		"response": "hey"
	}
]
```
> The keys/attributes would be the fields `"message"` and `"response"`, in which its value is assigned right after the colon (":"), being separated by braces ("{ }") and a comma indicating the next complete attribute.

## Working with Crystal
Opening your working directory, create a new directory to work with this project, in which we will need some "dependencies" before starting:
- Make sure you have Crystal installed correctly on your machine
- Prepare the favorite code editor in the directory that was created
- If you haven't installed Crystal yet click [here](https://crystal-lang.org/install) to learn more!
After opening your working directory, if you want to start `shards` to create a file to control the project, execute in your terminal:
```sh
$ shards init
```
Your `shard.yml` file should follow the following format:
```yml
name: json-reader

version: 0.1.0

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

description: |
	JSON reader

targets:
	test:
		main: src/main.cr

license: MIT
```

Now create a directory called `src`, in it we will put all our code worked using Crystal! Also create a directory called `content` in the root of the project to contain all our files in `JSON` format!
Therefore, just add a file in `JSON` format with the name you want in the `content` directory and add a `main.cr` file in the `src` directory, so we will have our tree:
```tree
json-reader/
├── content/
│ └── index.json
├── src/
│ └── main.cr
└── shard.yml
```
> Remembering that `shard.yml` only exists if you started shards inside your project!

## Hands-on (on keyboard)
First, let's create our JSON file, so, following the template above, create a `.json` file inside the `content` directory! With it we will manipulate the existing values there!
Now open your `src/main.cr` file in your favorite code editor so we can better manipulate our project!
Opening your file first let's import the `json` library to work with files in this format, so add in your code:
```crystal
require "json"
```
> Every library can be added with the `require` command!

In order to inform which file will be read, we will perform a simple pass by argument/option when running our project! So, if you want to go a little deeper into passing arguments on the command line with Crystal, click [here](https://crystal-lang.org/api/1.8.2/toplevel.html). In this way, let's save the content of the JSON file in a variable:
```crystal
content = File.read("content/#{ARGV[0]}")
```
> To receive the first argument/option we will use `ARGV[0]`, after all, position 0 is the first in the array of values passed as options! The `content` at the beginning means that we only need to pass the file name, after all, a file with the same name will already be searched for in the specified directory!

Right, but there is still the case of the user not adding any option, right? In this case, the code should not even continue, after all, if a JSON file is not specified, then, we cannot carry out the manipulation! So, before that, we add a simple check:
```crystal
if ARGV.size != 1
	puts "You must pass the filename as a parameter!"
	exit 1
end
```
> The length being different from 1 shows that either more than one argument was passed or none at all, so in both cases we must reject the execution!

There are a few ways to parse our JSON, let's start with the simplest: JSON with unique keys! See the example JSON below:
```json
{
	"test": "hi",
	"test2": "bye"
}
```
> Notice that this format does not have square brackets at the beginning, indicating that it is not an array!

To perform the `parse` we will use a native Crystal function to read a JSON and then convert it into a Hash of Strings!
```crystal
hash_content = Hash(String, String).from_json(content)

puts hash_content # {"test" => "hi", "test2" => "bye"}
```
> In this way, we are going to assemble a variable named `hash_content`, having in its content a hash with a key value from String to String, bringing this data from a JSON

So our final code will look something like this:
```crystal
require "json"

# Check arguments being equal to 1
if ARGV.size != 1
	puts "You must pass the filename as a parameter!"
	exit 1
end

# Loading the contents of the JSON file
content = File.read("content/#{ARGV[0]}")

# Transforming JSON file data into a String Hash => String
hash_content = Hash(String, String).from_json(content)

# Printing the contents of the variable
puts hash_content # {"test" => "hi", "test2" => "bye"}
```

We can run it in two ways:
- Using shards:
	- ```$ shards run -- content.json```
- Using the `crystal` itself:
	- ```$ crystal run src/main.cr content.json```

Okay, but what if we just wanted the `test` key returned?
```crystal

puts hash_content["test"] # hi

```

Thus, we were able to manipulate our data in the JSON file, being able to bring the specific keys for each item!

## JSON Arrays
Okay, but most of the JSONs we're going to find need to be "parsed" using Array formats... How can we do that? Well, there are two specific ways to accomplish this task, let's go?

### First form: Hash Array
Basically let's assemble an array with its internal type being hashed! But how would that look in practice? Well, first I'll go over the JSON file we'll be working with:
```json
[
	{
		"test": "hello"
	},
	{
		"test2": "hey"
	}
]
```
Let's first define that we are working with an array, but, when defining the array type, let's change it to `Hash(String, String)`, see:
```crystal
json = Array(Hash(String, String)).from_json(content)

puts json # [{"test" => "hello"}, {"test2" => "hey"}]
```
Notice that we've basically wrapped the previous hash format in the array, correct? So we can work with our arrays of JSONs the way we prefer! But, how could I bring only the contents of the "test" key? Simple, see two examples below:
```crystal
puts json[0] # {"test" => "hello"}

puts json[0]["test"] # hello
```
So we can manipulate our JSON!
If the key format is not always String, we can still create an `alias` to manipulate the types passed by Hash or just add a pipe operator ("|") demonstrating that the type can vary, so it would be something like:

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

### Second way: File.open
We can also use `File.open` to open a file and work with its content (in which I leave the credits to [cherry](https://twitter.com/cherry_ramatis) for being on [live](twitch. tv/gutolanjoni) and leaving this very important detail), without the need to create a variable to carry out this work (as we were doing with the `content` variable). See an implementation below:

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

puts json # [{"test" => "hello"}, {"test2" => "hey"}]

puts json[0] # {"test" => "hello"}

puts json[0]["test"] # hello
```
> In this example we perform a simple "parse" of values and update them in the variable named `json`, being able to work with the values in the same format as before!

Thus, our complete project ends up in the second shape! Here's how our finished code looks:
```crystal
require "json"

if ARGV.size != 1
	puts "You must pass the filename as a parameter!"
	exit 1
end

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

puts json # [{"test" => "hello"}, {"test2" => "hey"}]
```

## Finalization
With this guide and small article you learned how to manipulate JSON files and data coming from other files using Crystal, based on the key-value model that is offered, being able to create commands, specific readings, among other types of projects! To access the code developed in this article just click [here](https://github.com/lanjoni/json-reader-crystal)!
See you next time, see you later! Crystallize your day even more! 💎 🖤