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?
[
{
"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 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:
$ shards init
Your shard.yml
file should follow the following format:
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:
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:
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. In this way, let's save the content of the JSON file in a variable:
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! Thecontent
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:
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:
{
"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!
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:
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?
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:
[
{
"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:
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:
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:
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 for being on live 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:
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:
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!
See you next time, see you later! Crystallize your day even more! 💎 🖤
Top comments (2)
Nice article!
PS: indention in Crystal is usually 2 spaces 😉
Awesome content! Really like the ability to type check from_json calls