From 65f75d86e6774f6a2dd890070f610e5b98da45db Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Tue, 19 Nov 2019 00:24:20 +0100 Subject: [PATCH] Add id based database load and save functions Add functions to save and load a Map of objects indexed by the id property in the objects. --- lib/database.js | 79 +++++++++++++++++++++++++++++++++-- test/json/array_null.json | 1 + test/json/array_object.json | 1 + test/json/load_array_map.json | 1 + test/json/save_map.json | 10 +++++ test/lib/database.js | 75 ++++++++++++++++++++++++++++++++- 6 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 test/json/array_null.json create mode 100644 test/json/array_object.json create mode 100644 test/json/load_array_map.json create mode 100644 test/json/save_map.json diff --git a/lib/database.js b/lib/database.js index 69f7ae93..43a67f27 100644 --- a/lib/database.js +++ b/lib/database.js @@ -31,9 +31,10 @@ function mapToObject(map) { } /** - * Load JSON file as a Map + * Load JSON object file as a Map * - * Loads the JSON file specified by path an converts it into a JavaScript Map. + * Loads the JSON file specified by path containing an object with + * properties as key value pairs and converts it into a JavaScript Map. * Returns an empty Map if the file does not exist. * * @param {string} filePath - The path to the JSON file to load. @@ -71,7 +72,7 @@ async function loadJsonAsMap(filePath) { } /** - * Save a Map as a JSON file + * Save a Map as a JSON object file * * Save the content of a key-value Map with only string keys as a JSON file. * If the directory the file is to be saved into doesn't exist it will be @@ -87,6 +88,76 @@ async function saveMapAsJson(filePath, map) { await fs.outputFile(filePath, JSON.stringify(obj, null, 4)); } +/** + * Load JSON array file as a Map + * + * Loads the JSON file specified by path containing an array of objects that + * each have an id property into a Map from the id to the object. + * + * @param {string} filePath - The path to the JSON file to load. + * @throws {Error} If JSON file did not contain an array. + * @throws {Error} If there are objects in the array without an id property. + * @throws {Error} If an error occured reading the file. + */ +async function loadJsonArrayAsMap(filePath) { + try { + let content = await fs.readFile(filePath); + let parsed = JSON.parse(content); + + if (parsed === null) { + throw new Error(`Expected array but got null`); + } + + if (typeof parsed !== "object") { + throw new Error(`Expected array but got ${typeof parsed}`); + } + + if (!(parsed instanceof Array)) { + throw new Error(`Expected array but got object`); + } + + let map = new Map(); + for (let element of parsed) { + if (element === null || element instanceof Array || typeof element !== "object") { + throw new Error(`Expected all elements to be objects`); + } + + if (element.id === undefined) { + throw new Error(`Expected all elements to have an id property`); + } + + map.set(element.id, element); + } + return map; + + } catch (err) { + if (err.code == "ENOENT") { + // File does not exist, return empty map + return new Map(); + + } else { + throw err; + } + } +} + +/** + * Save a Map as a JSON array file + * + * Save the values of a Map with objects containing an id property that is + * equal to the key the objects are stored at into a JSON file. + * + * @param {string} filePath - The path to the JSON file that will be saved. + * @param {Map} map - + * Map with objects containing an id property equal to the key they are + * stored under. + * @throws {Error} if an error occured writing to the file. + */ +async function saveMapAsJsonArray(filePath, map) { + await fs.outputFile(filePath, JSON.stringify([...map.values()], null, 4)); +} + + function checkName(name) { if (typeof name !== "string") { @@ -227,5 +298,7 @@ module.exports = { mapToObject, loadJsonAsMap, saveMapAsJson, + loadJsonArrayAsMap, + saveMapAsJsonArray, ItemDatabase, } diff --git a/test/json/array_null.json b/test/json/array_null.json new file mode 100644 index 00000000..62864b31 --- /dev/null +++ b/test/json/array_null.json @@ -0,0 +1 @@ +[null] diff --git a/test/json/array_object.json b/test/json/array_object.json new file mode 100644 index 00000000..93d51406 --- /dev/null +++ b/test/json/array_object.json @@ -0,0 +1 @@ +[{}] diff --git a/test/json/load_array_map.json b/test/json/load_array_map.json new file mode 100644 index 00000000..996277e8 --- /dev/null +++ b/test/json/load_array_map.json @@ -0,0 +1 @@ +[{"id":"a"},{"id":"b"}] diff --git a/test/json/save_map.json b/test/json/save_map.json new file mode 100644 index 00000000..7c1a6687 --- /dev/null +++ b/test/json/save_map.json @@ -0,0 +1,10 @@ +[ + [ + "c", + {} + ], + [ + "d", + "foo" + ] +] \ No newline at end of file diff --git a/test/lib/database.js b/test/lib/database.js index 8956fed3..ad78688c 100644 --- a/test/lib/database.js +++ b/test/lib/database.js @@ -60,7 +60,7 @@ describe("lib/database", function() { describe("saveMapAsJson()", function() { it("should save a mapping as JSON", async function() { - let testFile = 'test/json/save_map.json'; + let testFile = 'test/temp/save_map.json'; async function deleteTestFile() { try { await fs.unlink(testFile); @@ -81,7 +81,80 @@ describe("lib/database", function() { await fs.readFile(testFile, {encoding: 'utf-8'}), '{\n "c": {},\n "d": "foo"\n}' ); + }); + }); + + describe("loadJsonArrayAsMap()", function() { + let badTypes = ['null', 'object', 'number', 'string', 'boolean']; + + for (let type of badTypes) { + it(`should reject on ${type} JSON`, async function() { + await assert.rejects( + database.loadJsonArrayAsMap(`test/json/${type}.json`), + new Error(`Expected array but got ${type}`) + ); + }); + } + + it("should reject on null element", async function() { + await assert.rejects( + database.loadJsonArrayAsMap('test/json/array_null.json'), + new Error("Expected all elements to be objects") + ); + }); + + it("should reject on element missing id", async function() { + await assert.rejects( + database.loadJsonArrayAsMap('test/json/array_object.json'), + new Error("Expected all elements to have an id property") + ); + }); + + it("should work on empty array JSON", async function() { + assert.deepEqual( + await database.loadJsonArrayAsMap('test/json/array.json'), + new Map() + ); + }); + + it("should work on array JSON", async function() { + assert.deepEqual( + await database.loadJsonArrayAsMap('test/json/load_array_map.json'), + new Map([['a', {id: 'a'}], ['b', {id: 'b'}]]) + ); + }); + + it("should give an empty Map for non-existant file", async function() { + assert.deepEqual( + await database.loadJsonArrayAsMap('test/json/does-not-exist'), + new Map() + ); + }); + }); + + describe("saveMapAsJsonArray()", function() { + it("should save a mapping as JSON", async function() { + let testFile = 'test/temp/save_array_map.json'; + async function deleteTestFile() { + try { + await fs.unlink(testFile); + } catch (err) { + /* istanbul ignore if */ + if (err.code !== 'ENOENT') { + throw err; + } + } + } + await deleteTestFile(); + await database.saveMapAsJsonArray( + testFile, new Map([['c', {id: 'c'}], ['d', {id: 'd'}]]) + ); + + assert.equal( + await fs.readFile(testFile, {encoding: 'utf-8'}), + '[\n {\n "id": "c"\n },\n {\n "id": "d"\n }\n]' + ); }); });