Bootcamp
Search…
2.13.3: JSON File Storage Module - Summary

Introduction

In 2.13.2: JSON File Storage Module - Passing Callbacks we learned how to abstract repeated code into jsonFileStorage such that the callback functions we pass to it contain only the custom logic required by each client app. Below is the transformed code for jsonFileStorage that receives callbacks from client apps and handles boilerplate JSON file reading and writing code.

Final JSON File Storage Module

The following is a summary of our modifications from the previous module. We have also made a few additional edits.
  1. 1.
    The callback passed to write, read, and add functions now accepts 2 params instead of 1. The read or write error is the 1st parameter, and JSON content JS Object is 2nd param. The error param allows the client app to handle the error in their own custom way. For example, each app or feature may wish to have its own distinct popup style or error message.
  2. 2.
    We created an edit function that combines logic from read and write to help the client easily edit JSON content. See the implementation and explanation below for details.
  3. 3.
    The add function is now built on edit and adds an element to an array at a specified key, instead of adding a new key-value pair to the JSON. We made this change because the add-to-array functionality is more common than the add-new-key functionality.
    1. 1.
      At first glance we may feel add is overly specific for the generic jsonFileStorage module. However, the functionality is common enough that we felt it deserves its own function in the generic module.

jsonFileStorage.js

import { readFile, writeFile } from 'fs';
​
/**
* Add a JS Object to an array of Objects in a JSON file
* @param {string} filename - Name of JSON file
* @param {object} jsonContentObj - The content to write to the JSON file
* @param {function} callback - The callback function to execute on error or success
* Callback takes write error as 1st param and JS Object as 2nd param.
* @returns undefined
*/
export function write(filename, jsonContentObj, callback) {
// Convert content object to string before writing
const jsonContentStr = JSON.stringify(jsonContentObj);
​
// Write content to DB
writeFile(filename, jsonContentStr, (writeErr) => {
if (writeErr) {
console.error('Write error', jsonContentStr, writeErr);
// Allow each client app to handle errors in their own way
callback(writeErr, null);
return;
}
console.log('Write success!');
// Call client-provided callback on successful write
callback(null, jsonContentStr);
});
}
​
/**
* Add a JS Object to an array of Objects in a JSON file
* @param {string} filename - Name of JSON file
* @param {function} callback - The callback function to execute on error or success
* Callback takes read error as 1st param and JS Object as 2nd param.
* @returns undefined
*/
export function read(filename, callback) {
const handleFileRead = (readErr, jsonContentStr) => {
if (readErr) {
console.error('Read error', readErr);
// Allow client to handle error in their own way
callback(readErr, null);
return;
}
​
// Convert file content to JS Object
const jsonContentObj = JSON.parse(jsonContentStr);
​
// Call client callback on file content
callback(null, jsonContentObj);
};
​
// Read content from DB
readFile(filename, 'utf-8', handleFileRead);
}
​
/**
* Add a JS Object to an array of Objects in a JSON file
* @param {string} filename - Name of JSON file
* @param {function} callback - The callback function to execute on error or success
* Callback takes read error as 1st param and JS Object as 2nd param.
* @returns undefined
*/
export function edit(filename, readCallback, writeCallback) {
// Read contents of target file and perform callback on JSON contents
read(filename, (readErr, jsonContentObj) => {
// Exit if there was a read error
if (readErr) {
console.error('Read error', readErr);
readCallback(readErr, null);
return;
}
​
// Perform custom edit operations here.
// jsonContentObj mutated in-place because object is mutable data type.
readCallback(null, jsonContentObj);
​
// Write updated content to target file.
write(filename, jsonContentObj, writeCallback);
});
}
​
/**
* Add a JS Object to an array of Objects in a JSON file
* @param {string} filename - Name of JSON file
* @param {string} key - The key in the JSON file whose value is the target array
* @param {string} input - The value to append to the target array
* @param {function} callback - The callback function to execute on error or success
* Callback takes read or write error as 1st param and written string as 2nd param.
* @returns undefined
*/
export function add(filename, key, input, callback) {
edit(
filename,
(err, jsonContentObj) => {
// Exit if there was an error
if (err) {
console.error('Edit error', err);
callback(err);
return;
}
​
// Exit if key does not exist in DB
if (!(key in jsonContentObj)) {
console.error('Key does not exist');
// Call callback with relevant error message to let client handle
callback('Key does not exist');
return;
}
​
// Add input element to target array
jsonContentObj[key].push(input);
},
// Pass callback to edit to be called after edit completion
callback
);
}

Example Usage of add

If data.json looked like the following...

data.json

{
"people" : []
}
...and we ran a command like the following...
node index.js add kai '435 Jalan Bukit Merah'
... the following code would add an entry into the people array in data.json.

index.js

import { add } from './jsonFileStorage.js';
​
// If the user specifies add functionality
if (process.argv[2] === 'add') {
// Name and address are 2nd and 3rd parameters respectively
const name = process.argv[3];
const address = process.argv[4];
​
// Create JS Object of data to store in DB
const person = {
name: name,
address: address,
};
​
// Tell jsonFileStorage to store new person in the people array in data.json.
// The callback is optional, and allows us to perform logic with updated data,
// e.g. update the UI.
add('data.json', 'people', person, (err) => {
if (!err) {
console.log('Done!');
}
});
}

Example Usage of Edit

If we want to edit our JSON DB in ways other than add, we can use edit to perform custom edit logic in a concise way. Note how much more concise this code is than the code from 2.ICE.5: Reading and Writing to JSON.

index.js (snippet)

import { edit } from './jsonFileStorage.js';
​
edit('data.json', (err, jsonContentObj) => {
// If no error, edit the content
if (!err) {
jsonContentObj['people'][0].name = 'Terminator';
}
});