Have you ever written a script in Ruby and suddenly realized that the data necessary for your script to run smoothly makes up for almost half of your code? Have you ever felt that your script looks cluttered, disorganized, and difficult to read and comprehend? Many of us certainly have. Well, there’s a simple solution in the form of extracting datasets to an external file. And what better format to save those datasets than the ever-present JSON?
First things first: let’s take a quick look at the basics of writing and reading a file in Ruby.
Ruby File Handling
In Ruby, File Handling is a way of processing files, which includes creating new files, reading and modifying existing ones, renaming files, and deleting them. Common modes for File Handling are read-only mode (‘r’), write-only mode (‘w’), read and write mode (‘r+’ and ‘w+’), write-only append mode (‘a’), and read and write append mode (‘a+’). The two append modes will not rewrite the previous file contents: instead, they will append the data if the file already exists; otherwise, they will create a completely new file.
Basically, if we want to create a new file with read and write permission, we will use File.new() method, provide a desired filename and an appropriate mode. After that, we can use the syswrite method to write the content into a new file:
data_file = File.new('example.txt', 'w+') data_file.syswrite('Sample text to be written into the file') data_file.close()
A text file ‘example.txt’, which contains the string ‘Sample text to be written into the file’, will be created and eventually closed to free up memory and system resources. Note that if we want some string to be written in two (or more) lines, we should use the ‘\n’ line separator and double quotation marks like this:
data_file.syswrite("Sample text to be written\ninto the file".)
There are three different methods to read the file:
- read method will return the entire content of a file;
- readlines method will return the content as an array of lines, and
- sysread(n) method will return the first n characters from a file, including whitespaces.
So, for example, if we want to read the entire file we have just created, we should execute the following code:
data_file = File.open('example.txt', 'r') puts data_file.read
We can also rename the file with:
data_file = File.rename('example.txt', 'another_example.txt')
Introduction to JSON File
Before we dive into how to read and write JSON files in Ruby, let us briefly remind you what a JSON file is. In a nutshell, JSON (JavaScript Object Notation) is a standard data-interchange format. A JSON file stores simple data structures and objects and is the easiest and simplest way to transmit data between a web application and a server – which makes it extremely popular for APIs, as it enables developers to work across multiple languages, including C, C++, Java, JavaScript, Python, and Ruby, among others. JSON files are lightweight, text-based, and easy for humans to read and write – as well as for machines to parse and generate – and they can be edited using nothing more than a simple text editor.
Reading and Parsing JSON Files
Manipulating JSON data in Ruby can be done easily with the JSON gem. This gem provides an API for parsing JSON from text, as well as generating JSON files from arbitrary Ruby objects.
Once the JSON gem is installed, you need to call it with the require ‘json’. Note that Ruby’s require method is used to import all class and method definitions from another file and execute all its statements. Furthermore, this method also keeps track of which files have been already required – meaning that if you have called require ‘json’ in your main file (for instance, init_rspec.rb file), any subsequent calls to require will have no effect as the file has already been loaded.
Now, let’s take a closer look at actually reading and writing JSON files in Ruby.
In order for us to read a specific JSON file, we have to provide the path to the file that we would like to read and save it as a variable.
require 'json' data_file = File.read('./name_of_file_that_we_want_to_read.json')
Note that it is a common practice to create a ./lib folder in your Ruby program’s directory, so the path to your file might look more like this:
./lib/some_directory/name_of_file_that_we_want_to_read.json
The next step would be to parse the data from the JSON file (the one we’ve just read) and store it in a hash. We can call that variable whatever we want, for example, ‘first_hash’.
first_hash = JSON.parse(data_file)
Example
Let’s say we have a JSON file called ‘london.json’, with some basic information about the capital of England and a short list of places to visit in the city. The file looks either like this:
{ "city":"London", "country":"England", "population":"8,799,800", "website":"www.london.gov.uk", "places_to_visit": { "1":"Westminster Abbey", "2":"Buckingham Palace", "3":"The London Eye", "4":"Trafalgar Square" } }
or like this:
{"city":"London","country":"England","population":"8,799,800","website":"www.london.gov.uk","places_to_visit":{"1":"Westminster Abbey","2":"Buckingham Palace","3":"The London Eye","4":"Trafalgar Square"}}
If we want to transform this file into a hash, we need to read it, save it into a variable, and then parse that variable. So, this is what we need to do:
require 'json' data_file = File.read('./london.json') first_hash = JSON.parse(data_file)
Writing and Modifying JSON Files
Now, let’s say we want to change our itinerary and choose some other places to visit. To do that, we need to make some changes to the hash we have already saved as the ’first_hash” variable.
first_hash['places_to_visit']['3'] = 'Madame Tussauds'
Then, we need to run the write command in order to write those changes into our JSON file.
File.write('./london.json', JSON.dump(first_hash))
This is how our JSON file should look after the changes have been written.
{"city":"London","country":"England","population":"8,799,800","website":"www.london.gov.uk","places_to_visit":{"1":"Westminster Abbey","2":"Buckingham Palace","3":"Madame Tussauds","4":"Trafalgar Square"}}
It is also possible to add new places to visit (instead of overwriting the existing ones)…
first_hash['places_to_visit']['5'] = 'Wembley Stadium'
{"city":"London","country":"England","population":"8,799,800","website":"www.london.gov.uk","places_to_visit":{"1":"Westminster Abbey","2":"Buckingham Palace","3":"Madame Tussauds","4":"Trafalgar Square","5":"Wembley Stadium"}}
…or additional info about the city. New info will be added at the end of the JSON file, but since an object in JSON is defined on json.org as ‘an unordered set of name/value pairs’, the order itself is, by definition, not significant.
first_hash['founded'] = 'AD 47'
{"city":"London","country":"England","population":"8,799,800","website":"www.london.gov.uk","places_to_visit":{"1":"Westminster Abbey","2":"Buckingham Palace","3":"Madame Tussauds","4":"Trafalgar Square","5":"Wembley Stadium"},"founded":"AD 47"}
Summary Example
So, to sum things up, imagine you started with those 12 lines in your Ruby script…
city_data = { 'city' => 'London', 'country' => 'England', 'population' => '8,799,800', 'website' => 'www.london.gov.uk', 'places_to_visit' => { '1' => 'Westminster Abbey', '2' => 'Buckingham Palace', '3' => 'The London Eye', '4' => 'Trafalgar Square' } }
…which you used to create a JSON file ‘london.json’ that would look like this:
{"city":"London","country":"England","population":"8,799,800","website":"www.london.gov.uk","places_to_visit":{"1":"Westminster Abbey","2":"Buckingham Palace","3":"The London Eye","4":"Trafalgar Square"}}
In order to read those data and use it in your script, the following three lines (or two, if you have previously called require ‘json’) are all you need:
require 'json' data_file = File.read('./london.json') city_data = JSON.parse(data_file)
The initial 12-line code becomes three lines, and the script starts looking prettier. Of course, the larger the dataset, the greater the benefit of dataset extraction to an external file.
Note that the very same JSON file can be used across multiple scripts. If needed, this file can be renamed, appended, or tweaked either manually or – as demonstrated in this article – with only several lines within the script.
Conclusion
With the ability to write and read JSON files in Ruby, we can easily save and access important data from our scripts following the JSON standard. Extracting datasets to a JSON file – especially if we are repeatedly using the same or similar datasets – will make our scripts look more compact and easier to read.
“Writing and reading JSON files in Ruby” Tech Bite was brought to you by Damir Memić, Junior Quality Assurance Engineer at Atlantbh.
Tech Bites are tips, tricks, snippets or explanations about various programming technologies and paradigms, which can help engineers with their everyday job.