Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Creative Projects for Rust Programmers

You're reading from   Creative Projects for Rust Programmers Build exciting projects on domains such as web apps, WebAssembly, games, and parsing

Arrow left icon
Product type Paperback
Published in Jun 2020
Publisher Packt
ISBN-13 9781789346220
Length 404 pages
Edition 1st Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Carlo Milanesi Carlo Milanesi
Author Profile Icon Carlo Milanesi
Carlo Milanesi
Arrow right icon
View More author details
Toc

Table of Contents (14) Chapters Close

Preface 1. Rust 2018: Productivity 2. Storing and Retrieving Data FREE CHAPTER 3. Creating a REST Web Service 4. Creating a Full Server-Side Web App 5. Creating a Client-Side WebAssembly App Using Yew 6. Creating a WebAssembly Game Using Quicksilver 7. Creating a Desktop Two-Dimensional Game Using ggez 8. Using a Parser Combinator for Interpreting and Compiling 9. Creating a Computer Emulator Using Nom 10. Creating a Linux Kernel Module 11. The Future of Rust 12. Assessments 13. Other Books You May Enjoy

Reading and writing a JSON file

For storing data that is more complex than that which is stored in a configuration file, JSON format is more appropriate. This format is quite popular, particularly among those who use the JavaScript language.

We are going to read and parse the data/sales.json file. This file contains a single anonymous object, which contains two arrays—"products" and "sales".

The "products" array contains two objects, each one having three fields:

  "products": [
{
"id": 591,
"category": "fruit",
"name": "orange"
},
{
"id": 190,
"category": "furniture",
"name": "chair"
}
],

The "sales" array contains three objects, each one containing five fields:

"sales": [
{
"id": "2020-7110",
"product_id": 190,
"date": 1234527890,
"quantity": 2.0,
"unit": "u."
},
{
"id": "2020-2871",
"product_id": 591,
"date": 1234567590,
"quantity": 2.14,
"unit": "Kg"
},
{
"id": "2020-2583",
"product_id": 190,
"date": 1234563890,
"quantity": 4.0,
"unit": "u."
}
]

The information in the arrays is about some products to sell and some sale transactions associated with those products. Notice that the second field of each sale ("product_id") is a reference to a product, and so it should be processed after the corresponding product object has been created.

We will see a pair of programs with the same behavior. They read the JSON file, increment the quantity of the second sale object by 1.5, and then save the whole updated structure into another JSON file.

Similarly to the TOML format case, there can also be a dynamic parsing technique used for JSON files, where the existence and type of any data field is checked by the application code, and a static parsing technique, where it uses the deserialization library to check the existence and type of any field.

So, we have two projects: json_dynamic and json_static. To run each of them, open its folder and type in cargo run ../data/sales.json ../data/sales2.json. The program will not print anything, but it will read the first file specified in the command line and create the second file that is specified.

The created file is similar to the read file, but with the following differences:

  • The fields of the file created by json_dynamic are sorted in alphabetical order, while the fields of the file created by json_static are sorted in the same order as in the Rust data structure.
  • The quantity of the second sale is incremented from 2.14 to 3.64.
  • The final empty line is removed in both created files.

Now, we can see the implementations of the two techniques of serialization and deserialization.

The json_dynamic project

Let's look at the source code of the project:

  1. This project gets the pathnames of two files from the command line—the existing JSON file ("input_path") to read into a memory structure and a JSON file to create ("output_path") by saving the loaded structure, after having modified it a bit.
  2. Then, the input file is loaded into the string named sales_and_products_text and the generic serde_json::from_str::<Value> function is used to parse the string into a dynamically typed structure representing the JSON file. This structure is stored in the sales_and_products local variable.

Imagine that we want to change the quantity sold by the second sale transaction, incrementing it by 1.5 kilograms:

  1. First, we must get to this value using the following expression:
sales_and_products["sales"][1]["quantity"]
  1. This retrieves the "sales" sub-object of the general object. It is an array containing three objects.
  1. Then, this expression gets the second item (starting from zero ([1])) of this array. This is an object representing a single sale transaction.
  2. After this, it gets the "quantity" sub-object of the sale transaction object.
  3. The value we have reached has a dynamic type that we think should be serde_json::Value::Number, and so we make a pattern matching with this type, specifying the if let Value::Number(n) clause.
  4. If all is good, the matching succeeds and we get a variable named n—containing a number, or something that can be converted into a Rust floating-point number by using the as_f64 function. Lastly, we can increment the Rust number and then create a JSON number from it using the from_f64 function. We can then assign this object to the JSON structure using the same expression we used to get it:
sales_and_products["sales"][1]["quantity"]
= Value::Number(Number::from_f64(
n.as_f64().unwrap() + 1.5).unwrap());
  1. The last statement of the program saves the JSON structure to a file. Here, the serde_json::to_string_pretty function is used. As the name suggests, this function adds formatting whitespace (blanks and new lines) to make the resulting JSON file more human-readable. There is also the serde_json::to_string function, which creates a more compact version of the same information. It is much harder for people to read, but it is somewhat quicker to process for a computer:
std::fs::write(
output_path,
serde_json::to_string_pretty(&sales_and_products).unwrap(),
).unwrap();

The json_static project

If, for our program, we are sure that we know the structure of the JSON file, a statically typed technique can and should be used instead. It is shown in the json_static project. The situation here is similar to that of the projects processing the TOML file.

The source code of the static version first declares three structs—one for every object type contained in the JSON file we are going to process. Each struct is preceded by the following attribute:

#[derive(Deserialize, Serialize, Debug)]
Let's understand the preceding snippet:
  • The Deserialize trait is required to parse (that is, read) JSON strings into this struct.
  • The Serialize trait is required to format (that is, write) this struct into a JSON string.
  • The Debug trait is just handy for printing this struct on a debug trace.

The JSON string is parsed using the serde_json::from_str::<SalesAndProducts> function. Then, the code to increment the quantity of sold oranges becomes quite simple:

sales_and_products.sales[1].quantity += 1.5

The rest of the is unchanged.

You have been reading a chapter from
Creative Projects for Rust Programmers
Published in: Jun 2020
Publisher: Packt
ISBN-13: 9781789346220
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image