diff --git a/CHANGELOG b/CHANGELOG index 8e25035..ce49674 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,5 @@ -Release 0.6 +2021-05-25 Release 0.6 + - add support for object-style JSON - remove Source.getMatchingColumns() - add Source.getColumnIndex() and Source.getColumnIndices() - many speed optimisations diff --git a/hxl.js b/hxl.js index a2099a1..b040744 100644 --- a/hxl.js +++ b/hxl.js @@ -6,8 +6,8 @@ * operations that are useful to support mapping and visualisation. * * @author David Megginson - * @date 2021-04-27 - * @version 0.4 + * @date 2021-05-25 + * @version 0.6 */ //////////////////////////////////////////////////////////////////////// @@ -58,9 +58,66 @@ hxl.log = function (message) { * @return a new {@link hxl.classes.Dataset} */ hxl.wrap = function (rawData) { + + /** + * Convert object-style JSON to row-style JSON + */ + function convertToRows (data) { + let result = [[]]; + let columnCount = 0; + let columns = {}; + + data.forEach(item => { + + let row = []; + + if (typeof(item) !== "object" || item === null) { + // expecting objects + console.error("Expected array of objects", item); + throw new Error("Expected array of objects in hxl.wrap()"); + } + + Object.keys(item).forEach(key => { + if (!(key in columns)) { + // we haven't seen this key yet; note it + columns[key] = columnCount++; + + // add to header row + result[0][columns[key]] = key; + } + // add the value to the appropriate position in the row + row[columns[key]] = item[key]; + }); + + // remove null and undefined values + for (var i = 0; i < columnCount; i++) { + if (row[i] === undefined || row[i] === null) { + row[i] = ""; + } + } + + // add the row to the dataset + result.push(row); + }); + + return result; + } + + if (!Array.isArray(rawData)) { + console.error("Expected an array", rawData); + throw new Error("Expected an array in hxl.wrap()"); + } + if (rawData.length == 0) { + console.error("Empty array", rawData); + throw new Error("Empty array in hxl.wrap()"); + } + if (!Array.isArray(rawData[0])) { + rawData = convertToRows(rawData); + } return new hxl.classes.Dataset(rawData); }; + /** * Load a remote HXL dataset asynchronously. * @@ -659,8 +716,8 @@ hxl.classes.Source.prototype.getColumnIndices = function(pattern) { } // Try for a cache hit - if (pattern.toString() in this.cache) { - return this.cache[pattern.toString()]; + if (pattern.toString() in this.indexCache) { + return this.indexCache[pattern.toString()]; } let result = []; @@ -672,7 +729,7 @@ hxl.classes.Source.prototype.getColumnIndices = function(pattern) { result.push(i); } } - this.cache[pattern.toString()] = result; + this.indexCache[pattern.toString()] = result; return result; } diff --git a/test/test-dataset.js b/test/test-dataset.js index fdffae2..ffb426c 100644 --- a/test/test-dataset.js +++ b/test/test-dataset.js @@ -12,7 +12,7 @@ QUnit.module("hxl.classes.Dataset", { ['Org 2', '', 'Health', 'Mountain Province', '300'], ['Org 3', '', 'Protection', 'Coastal Province', '400'] ]; - this.dataset = new hxl.classes.Dataset(this.test_data); + this.dataset = new hxl.wrap(this.test_data); } }); @@ -20,6 +20,21 @@ QUnit.test("dataset created", function(assert) { assert.ok(this.dataset); }); +QUnit.test("object-style JSON", function(assert) { + let data = [ + {'#org': 'Org 1', '#sector+cluster': 'WASH', '#adm1': 'Coastal Province', '#reached': '200'}, + {'#org': 'Org 2', '#adm1': 'Mountain Province', '#reached': '300'}, + {'#org': 'Org 3', '#reached': '400', '#sector+cluster': 'Protection', '#adm1': 'Coastal Province'} + ]; + dataset = hxl.wrap(data); + assert.deepEqual(dataset.displayTags, ['#org', '#sector+cluster', '#adm1', '#reached']); + assert.deepEqual(dataset.rawData, [ + ["Org 1", "WASH", "Coastal Province", "200"], + ["Org 2", "", "Mountain Province", "300"], + ["Org 3", "Protection", "Coastal Province", "400"] + ]); +}); + QUnit.test("headers", function(assert) { assert.deepEqual(this.dataset.headers, this.test_data[1]); });