From 1cff37d5e1399f05ce6569a72ff481fa5aa812d8 Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Tue, 5 Mar 2019 14:41:16 -0800 Subject: [PATCH 01/25] typescript installed --- .gitignore | 1 + NestHydrationJS.js | 432 ----------------------- package-lock.json | 20 +- package.json | 8 +- spec/NestHydrationJS/multipleIds.spec.js | 53 +++ 5 files changed, 80 insertions(+), 434 deletions(-) delete mode 100644 NestHydrationJS.js create mode 100644 spec/NestHydrationJS/multipleIds.spec.js diff --git a/.gitignore b/.gitignore index 81e2efc..bce22e2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ * +!/lib/**/* !/spec !/spec/** !/.coveralls.yml diff --git a/NestHydrationJS.js b/NestHydrationJS.js deleted file mode 100644 index 9368cdc..0000000 --- a/NestHydrationJS.js +++ /dev/null @@ -1,432 +0,0 @@ -'use strict'; - -function nestHydrationJS() { - var NestHydrationJS, _; - - var isArray = require('lodash.isarray'); - var isFunction = require('lodash.isfunction'); - var keys = require('lodash.keys'); - var values = require('lodash.values'); - var isPlainObject = require('lodash.isplainobject'); - - NestHydrationJS = {}; - - NestHydrationJS.typeHandlers = { - NUMBER: function (cellValue) { - return parseFloat(cellValue); - }, - BOOLEAN: function (cellValue) { - return cellValue == true; - } - }; - - /* Creates a data structure containing nested objects and/or arrays from - * tabular data based on a structure definition provided by - * structPropToColumnMap. If structPropToColumnMap is not provided but - * the data has column names that follow a particular convention then a - * nested structures can also be created. - */ - NestHydrationJS.nest = function (data, structPropToColumnMap) { - var listOnEmpty, table, meta, struct, i, row, j, _nest, primeIdColumn; - - // VALIDATE PARAMS AND BASIC INITIALIZATION - - listOnEmpty = false; - - if (typeof structPropToColumnMap === 'undefined') { - structPropToColumnMap = null; - } - - if (data === null) { - return null; - } - - if (!isArray(structPropToColumnMap) && !isPlainObject(structPropToColumnMap) && structPropToColumnMap !== null && structPropToColumnMap !== true) { - throw new Error('nest expects param structPropToColumnMap to be an array, plain object, null, or true'); - } - - if (isPlainObject(data)) { - // internal table should be a table format but a plain object - // could be passed as the first (and only) row of that table - table = [data]; - } else if (isArray(data)) { - table = data; - } else { - throw Error('nest expects param data to be in the form of a plain object or an array of plain objects (forming a table)'); - } - - // structPropToColumnMap can be set to true as a tie break between - // returning null (empty structure) or an empty list - if (structPropToColumnMap === true) { - listOnEmpty = true; - structPropToColumnMap = null; - } - - if (structPropToColumnMap === null && table.length > 0) { - // property mapping not specified, determine it from column names - structPropToColumnMap = NestHydrationJS.structPropToColumnMapFromColumnHints(keys(table[0])); - } - - if (structPropToColumnMap === null) { - // properties is empty, can't form structure or determine content - // for a list. Assume a structure unless listOnEmpty - return listOnEmpty ? [] : null; - } else if (table.length === 0) { - // table is empty, return the appropriate empty result based on input definition - return isArray(structPropToColumnMap) ? [] : null; - } - - // COMPLETE VALIDATING PARAMS AND BASIC INITIALIZATION - - meta = NestHydrationJS.buildMeta(structPropToColumnMap); - - // BUILD FROM TABLE - - // defines function that can be called recursively - _nest = function (row, idColumn) { - var value, objMeta, obj, k, containingId, container, cell, cellValue, valueTypeFunction; - - value = row[idColumn]; - - // only really concerned with the meta data for this identity column - objMeta = meta.idMap[idColumn]; - - if (value === null) { - if (objMeta.default !== null && typeof objMeta.default !== 'undefined') { - value = objMeta.default; - } else { - return; - } - } - - if (typeof objMeta.cache[value] !== 'undefined') { - // object already exists in cache - if (objMeta.containingIdUsage === null) { - // at the top level, parent is root - return; - } - - containingId = row[objMeta.containingColumn]; - if (typeof objMeta.containingIdUsage[value] !== 'undefined' - && typeof objMeta.containingIdUsage[value][containingId] !== 'undefined' - ) { - // already placed as to-many relation in container, done - return; - } - - // not already placed as to-many relation in container - obj = objMeta.cache[value]; - } else { - // don't have an object defined for this yet, create it - obj = {}; - objMeta.cache[value] = obj; - - // copy in properties from table data - for (k = 0; k < objMeta.valueList.length; k++) { - cell = objMeta.valueList[k]; - cellValue = row[cell.column]; - if (cellValue !== null) { - if (isFunction(cell.type)) { - valueTypeFunction = cell.type; - } else { - valueTypeFunction = NestHydrationJS.typeHandlers[cell.type]; - } - if (valueTypeFunction) { - cellValue = valueTypeFunction(cellValue, cell.column, row); - } - } else if (typeof cell.default !== 'undefined') { - cellValue = cell.default; - } - - obj[cell.prop] = cellValue; - } - - // initialize empty to-many relations, they will be populated when - // those objects build themselves and find this containing object - for (k = 0; k < objMeta.toManyPropList.length; k++) { - obj[objMeta.toManyPropList[k]] = []; - } - - // initialize null to-one relations and then recursively build them - for (k = 0; k < objMeta.toOneList.length; k++) { - obj[objMeta.toOneList[k].prop] = null; - _nest(row, objMeta.toOneList[k].column); - } - } - - // link from the parent - if (objMeta.containingColumn === null) { - // parent is the top level - if (objMeta.isOneOfMany) { - // it is an array - if (struct === null) { - struct = []; - } - struct.push(obj); - } else { - // it is this object - struct = obj; - } - } else { - containingId = row[objMeta.containingColumn]; - container = meta.idMap[objMeta.containingColumn].cache[containingId]; - - if (container) { - if (objMeta.isOneOfMany) { - // it is an array - container[objMeta.ownProp].push(obj); - } else { - // it is this object - container[objMeta.ownProp] = obj; - } - } - - // record the containing id - if (typeof objMeta.containingIdUsage[value] === 'undefined') { - objMeta.containingIdUsage[value] = {}; - } - objMeta.containingIdUsage[value][containingId] = true; - } - }; - - // struct is populated inside the build function - struct = null; - - for (i = 0; i < table.length; i++) { - // go through each row of the table - row = table[i]; - - for (j = 0; j < meta.primeIdColumnList.length; j++) { - // for each prime id column (corresponding to a to-many relation or - // the top level) attempted to build an object - primeIdColumn = meta.primeIdColumnList[j]; - - _nest(row, primeIdColumn); - } - } - - return struct; - }; - - /* Create a data structure that contains lookups and cache spaces for quick - * reference and action for the workings of the nest method. - */ - NestHydrationJS.buildMeta = function (structPropToColumnMap) { - // internally defines recursive function with extra param. This allows cleaner API - var meta, _buildMeta, primeIdColumn; - - // recursive internal function - _buildMeta = function (structPropToColumnMap, isOneOfMany, containingColumn, ownProp) { - var propList, idProp, idColumn, i, prop, objMeta, subIdColumn; - - - propList = keys(structPropToColumnMap); - if (propList.length === 0) { - throw new Error('invalid structPropToColumnMap format - property \'' + ownProp + '\' can not be an empty array'); - } - - for (i = 0; i < propList.length; i++) { - prop = propList[i]; - if (structPropToColumnMap[prop].id === true) { - idProp = prop; - break; - } - } - - if (idProp === undefined) { - idProp = propList[0]; - } - - idColumn = structPropToColumnMap[idProp].column || structPropToColumnMap[idProp]; - - if (isOneOfMany) { - meta.primeIdColumnList.push(idColumn); - } - - objMeta = { - valueList: [], - toOneList: [], - toManyPropList: [], - containingColumn: containingColumn, - ownProp: ownProp, - isOneOfMany: isOneOfMany === true, - cache: {}, - containingIdUsage: containingColumn === null ? null : {}, - default: typeof structPropToColumnMap[idProp].default === 'undefined' ? null : structPropToColumnMap[idProp].default - }; - - for (i = 0; i < propList.length; i++) { - prop = propList[i]; - if (typeof structPropToColumnMap[prop] === 'string') { - // value property - objMeta.valueList.push({ - prop: prop, - column: structPropToColumnMap[prop], - type: undefined, - default: undefined - }); - } else if (structPropToColumnMap[prop].column) { - // value property - objMeta.valueList.push({ - prop: prop, - column: structPropToColumnMap[prop].column, - type: structPropToColumnMap[prop].type, - default: structPropToColumnMap[prop].default - }); - } else if (isArray(structPropToColumnMap[prop])) { - // list of objects / to-many relation - objMeta.toManyPropList.push(prop); - - _buildMeta(structPropToColumnMap[prop][0], true, idColumn, prop); - } else if (isPlainObject(structPropToColumnMap[prop])) { - // object / to-one relation - - subIdColumn = values(structPropToColumnMap[prop])[0]; - if (typeof subIdColumn === 'undefined') { - throw new Error('invalid structPropToColumnMap format - property \'' + prop + '\' can not be an empty object'); - } - - if (subIdColumn.column) { - subIdColumn = subIdColumn.column; - } - - objMeta.toOneList.push({ - prop: prop, - column: subIdColumn - }); - _buildMeta(structPropToColumnMap[prop], false, idColumn, prop); - } else { - throw new Error('invalid structPropToColumnMap format - property \'' + prop + '\' must be either a string, a plain object or an array'); - } - } - - meta.idMap[idColumn] = objMeta; - }; - - // this data structure is populated by the _buildMeta function - meta = { - primeIdColumnList: [], - idMap: {} - }; - - if (isArray(structPropToColumnMap)) { - if (structPropToColumnMap.length !== 1) { - throw new Error('invalid structPropToColumnMap format - can not have multiple roots for structPropToColumnMap, if an array it must only have one item'); - } - // call with first object, but inform _buildMeta it is an array - _buildMeta(structPropToColumnMap[0], true, null, null); - } else if (isPlainObject(structPropToColumnMap)) { - // register first column as prime id column - primeIdColumn = values(structPropToColumnMap)[0]; - if (typeof primeIdColumn === 'undefined') { - throw new Error('invalid structPropToColumnMap format - the base object can not be an empty object'); - } - - if (typeof primeIdColumn !== 'string') { - primeIdColumn = primeIdColumn.column; - } - - meta.primeIdColumnList.push(primeIdColumn); - - // construct the rest - _buildMeta(structPropToColumnMap, false, null, null); - } - - return meta; - }; - - /* Returns a property mapping data structure based on the names of columns - * in columnList. Used internally by nest when its propertyMapping param - * is not specified. - */ - NestHydrationJS.structPropToColumnMapFromColumnHints = function (columnList, renameMapping) { - var propertyMapping, prop, i, columnType, type, isId, column, pointer, navList, j, nav, renamedColumn, prevKeyList, k; - - if (typeof renameMapping === 'undefined') { - renameMapping = {}; - } - - propertyMapping = {base: null}; - - for (i = 0; i < columnList.length; i++) { - column = columnList[i]; - - columnType = column.split('___'); - - type = null; - isId = false; - for (j = 1; j < columnType.length; j++) { - if (columnType[j] === 'ID') { - isId = true; - } else if (typeof NestHydrationJS.typeHandlers[columnType[j]] !== 'undefined') { - type = columnType[j]; - } - } - - pointer = propertyMapping; // point to base on each new column - prop = 'base'; - - navList = columnType[0].split('_'); - - for (j = 0; j < navList.length; j++) { - nav = navList[j]; - - if (nav === '') { - if (pointer[prop] === null) { - pointer[prop] = [null]; - } - pointer = pointer[prop]; - prop = 0; - } else { - if (pointer[prop] === null) { - pointer[prop] = {}; - } - if (typeof pointer[prop][nav] === 'undefined') { - renamedColumn = typeof renameMapping[column] === 'undefined' - ? column - : renameMapping[column] - ; - if (type !== null || isId) { - // no longer a simple mapping, has need of the type or id properties - renamedColumn = {column: renamedColumn}; - } - if (type !== null) { - // detail the type in the column map if type provided - renamedColumn.type = type; - } - if (isId) { - // set the id property in the column map - renamedColumn.id = true; - // check for any existing id keys that are conflicting - prevKeyList = keys(pointer[prop]); - for (k = 0; k < prevKeyList.length; k++) { - if (pointer[prop][prevKeyList[k]].id === true) { - return 'invalid - multiple id - ' + pointer[prop][prevKeyList[k]].column + ' and ' + renamedColumn.column + ' conflict'; - } - } - } - pointer[prop][nav] = j === (navList.length - 1) - ? renamedColumn // is leaf node, store full column string - : null // iteration will replace with object or array - ; - } - pointer = pointer[prop]; - prop = nav; - } - } - } - - return propertyMapping.base; - }; - - /* Registers a custom type handler */ - NestHydrationJS.registerType = function (name, handler) { - if (NestHydrationJS.typeHandlers[name]) { - throw new Error('Handler with type, ' + name + ', already exists'); - } - - NestHydrationJS.typeHandlers[name] = handler; - }; - return NestHydrationJS; -} -module.exports = nestHydrationJS; diff --git a/package-lock.json b/package-lock.json index 1491ca2..15bb241 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,21 @@ { "name": "nesthydrationjs", - "version": "1.0.4", + "version": "1.0.6", "lockfileVersion": 1, "requires": true, "dependencies": { + "@types/lodash": { + "version": "4.14.122", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.122.tgz", + "integrity": "sha512-9IdED8wU93ty8gP06ninox+42SBSJHp2IAamsSYMUY76mshRTeUsid/gtbl8ovnOwy8im41ib4cxTiIYMXGKew==", + "dev": true + }, + "@types/node": { + "version": "11.10.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.10.4.tgz", + "integrity": "sha512-wa09itaLE8L705aXd8F80jnFpxz3Y1/KRHfKsYL2bPc0XF+wEWu8sR9n5bmeu8Ba1N9z2GRNzm/YdHcghLkLKg==", + "dev": true + }, "acorn": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz", @@ -4177,6 +4189,12 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "typescript": { + "version": "3.3.3333", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.3333.tgz", + "integrity": "sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw==", + "dev": true + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 7fd0f8a..76f3e60 100644 --- a/package.json +++ b/package.json @@ -30,10 +30,16 @@ "lodash.isplainobject": "^4.0.6" }, "devDependencies": { + "@types/lodash": "^4.14.122", + "@types/node": "^11.10.4", "coveralls": "^3.0.1", "eslint": "^4.19.1", "jasmine": "^3.1.0", - "nyc": "^11.7.3" + "nyc": "^11.7.3", + "typescript": "^3.3.3333" }, + "files": [ + "lib/**/*" + ], "author": "Bluedrop Peformance Learning" } diff --git a/spec/NestHydrationJS/multipleIds.spec.js b/spec/NestHydrationJS/multipleIds.spec.js new file mode 100644 index 0000000..64ef7c1 --- /dev/null +++ b/spec/NestHydrationJS/multipleIds.spec.js @@ -0,0 +1,53 @@ +'use strict'; + +var NestHydrationJS = require('../../NestHydrationJS')(); + +// fdescribe('NestHydrationJS', function () { +// describe('nest method', function () { +// describe('Documentation Example 1', function () { +// var result; +// beforeEach(function () { +// var table = [ +// {id: '1', title: 'Tabular to Objects', teacher_id: '1', teacher_name: 'David', lesson_id: '1', lesson_title: 'Definitions' }, +// {id: '1', title: 'Tabular to Objects', teacher_id: '1', teacher_name: 'David', lesson_id: '2', lesson_title: 'Table Data' }, +// {id: '1', title: 'Tabular to Objects', teacher_id: '1', teacher_name: 'David', lesson_id: '3', lesson_title: 'Objects' }, +// {id: '2', title: 'Column Names Define Structure', teacher_id: '2', teacher_name: 'Chris', lesson_id: '4', lesson_title: 'Column Names' }, +// {id: '2', title: 'Column Names Define Structure', teacher_id: '2', teacher_name: 'Chris', lesson_id: '2', lesson_title: 'Table Data' }, +// {id: '2', title: 'Column Names Define Structure', teacher_id: '2', teacher_name: 'Chris', lesson_id: '3', lesson_title: 'Objects' }, +// {id: '3', title: 'Object On Bottom', teacher_id: '1', teacher_name: 'David', lesson_id: '5', lesson_title: 'Non Array Input'} +// ]; +// var definition = [{ +// id: 'id', +// title: 'title', +// teacher: { +// id: 'teacher_id', +// name: 'teacher_name' +// }, +// lesson: [{ +// id: 'lesson_id', +// title: 'lesson_title' +// }] +// }]; +// result = NestHydrationJS.nest(table, definition); +// }); +// it('should match expected structure', function () { +// var expected = [ +// {id: '1', title: 'Tabular to Objects', teacher: {id: '1', name: 'David'}, lesson: [ +// {id: '1', title: 'Definitions'}, +// {id: '2', title: 'Table Data'}, +// {id: '3', title: 'Objects'} +// ]}, +// {id: '2', title: 'Column Names Define Structure', teacher: {id: '2', name: 'Chris'}, lesson: [ +// {id: '4', title: 'Column Names'}, +// {id: '2', title: 'Table Data'}, +// {id: '3', title: 'Objects'} +// ]}, +// {id: '3', title: 'Object On Bottom', teacher: {id: '1', name: 'David'}, lesson: [ +// {id: '5', title: 'Non Array Input'} +// ]} +// ]; +// expect(result).toEqual({}); +// }); +// }); +// }); +// }); From 49fc7c6d5324396cff0bef101af58211c2e24180 Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Wed, 6 Mar 2019 15:25:09 -0800 Subject: [PATCH 02/25] add source --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index bce22e2..8dca9b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ * -!/lib/**/* !/spec !/spec/** !/.coveralls.yml @@ -12,3 +11,4 @@ !/package-lock.json !/package.json !/README.md +!/src/**/* From 6ad01dab992bb2426411c1b017484cc41a745f24 Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Wed, 6 Mar 2019 15:28:36 -0800 Subject: [PATCH 03/25] add ts --- .gitignore | 15 +- lib/NestHydrationJS.d.ts | 52 ++++ lib/NestHydrationJS.js | 402 +++++++++++++++++++++++++++++++ src/NestHydrationJS.ts | 509 +++++++++++++++++++++++++++++++++++++++ tsconfig.json | 11 + 5 files changed, 975 insertions(+), 14 deletions(-) create mode 100644 lib/NestHydrationJS.d.ts create mode 100644 lib/NestHydrationJS.js create mode 100644 src/NestHydrationJS.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index 8dca9b4..c2658d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1 @@ -* - -!/spec -!/spec/** -!/.coveralls.yml -!/.editorconfig -!/.eslintrc -!/.travis.yml -!/CHANGELOG.md -!/NestHydrationJS.js -!/package-lock.json -!/package.json -!/README.md -!/src/**/* +node_modules/ diff --git a/lib/NestHydrationJS.d.ts b/lib/NestHydrationJS.d.ts new file mode 100644 index 0000000..71930e2 --- /dev/null +++ b/lib/NestHydrationJS.d.ts @@ -0,0 +1,52 @@ +declare module NestHydrationJS { + interface TypeHandlers { + [index: string]: TypeHandler; + } + interface TypeHandler { + (cellValue: any): any; + } + interface MetaValueProps { + prop: string; + column: string; + type?: string | TypeHandler; + default?: any; + } + interface Dictionary { + [index: string]: TValue; + } + interface MetaColumnData { + valueList: Array; + toOneList: Array; + toManyPropList: Array; + containingColumn: string | null; + ownProp: string | null; + isOneOfMany: boolean; + cache: Dictionary; + containingIdUsage: Dictionary> | null; + default?: any; + } + interface MetaData { + primeIdColumnList: Array; + idMap: { + [index: string]: MetaColumnData; + }; + } + interface DefinitionColumn { + column: string; + id?: boolean; + default?: any; + type?: string; + } + interface Definition { + [index: string]: DefinitionColumn | string | Definition | Definition[]; + } + class NestHydrationJS { + typeHandlers: TypeHandlers; + struct: object | Array | null; + nest(data: any, structPropToColumnMap: Definition | Definition[] | null | boolean): any; + buildMeta(structPropToColumnMap: Definition | Definition[]): MetaData; + registerType(name: string, handler: TypeHandler): void; + } +} +export default function (): NestHydrationJS.NestHydrationJS; +export {}; diff --git a/lib/NestHydrationJS.js b/lib/NestHydrationJS.js new file mode 100644 index 0000000..064c646 --- /dev/null +++ b/lib/NestHydrationJS.js @@ -0,0 +1,402 @@ +'use strict'; +Object.defineProperty(exports, "__esModule", { value: true }); +var isArray = require('lodash.isarray'); +var isFunction = require('lodash.isfunction'); +var keys = require('lodash.keys'); +var values = require('lodash.values'); +var isPlainObject = require('lodash.isplainobject'); +var NestHydrationJS; +(function (NestHydrationJS_1) { + var NestHydrationJS = /** @class */ (function () { + function NestHydrationJS() { + this.typeHandlers = { + NUMBER: function (cellValue) { + return parseFloat(cellValue); + }, + BOOLEAN: function (cellValue) { + return cellValue == true; + } + }; + this.struct = null; + } + /* Creates a data structure containing nested objects and/or arrays from + * tabular data based on a structure definition provided by + * structPropToColumnMap. If structPropToColumnMap is not provided but + * the data has column names that follow a particular convention then a + * nested structures can also be created. + */ + NestHydrationJS.prototype.nest = function (data, structPropToColumnMap) { + var _this = this; + var table; + // VALIDATE PARAMS AND BASIC INITIALIZATION + // Determines that on no results, and empty list is used instead of null. // NOTE: fact check this + var listOnEmpty = false; + if (typeof structPropToColumnMap === 'undefined') { + structPropToColumnMap = null; + } + if (data === null) { + return null; + } + if (!isArray(structPropToColumnMap) && !isPlainObject(structPropToColumnMap) && structPropToColumnMap !== null && structPropToColumnMap !== true) { + throw new Error('nest expects param structPropToColumnMap to be an array, plain object, null, or true'); + } + if (isPlainObject(data)) { + // internal table should be a table format but a plain object + // could be passed as the first (and only) row of that table + table = [data]; + } + else if (isArray(data)) { + table = data; + } + else { + throw Error('nest expects param data to be in the form of a plain object or an array of plain objects (forming a table)'); + } + // structPropToColumnMap can be set to true as a tie break between + // returning null (empty structure) or an empty list + if (structPropToColumnMap === true) { + listOnEmpty = true; + structPropToColumnMap = null; + } + if (structPropToColumnMap === null && table.length > 0) { + // property mapping not specified, determine it from column names + // structPropToColumnMap = this.structPropToColumnMapFromColumnHints(keys(table[0])); + } + if (structPropToColumnMap === null) { + // properties is empty, can't form structure or determine content + // for a list. Assume a structure unless listOnEmpty + return listOnEmpty ? [] : null; + } + else if (table.length === 0) { + // table is empty, return the appropriate empty result based on input definition + return isArray(structPropToColumnMap) ? [] : null; + } + // COMPLETE VALIDATING PARAMS AND BASIC INITIALIZATION + var meta = this.buildMeta(structPropToColumnMap); + // BUILD FROM TABLE + // defines function that can be called recursively + var _nest = function (row, idColumn) { + // Obj is the actual object that will end up in the final structure + var obj; + // Value here is really the id + var value = row[idColumn]; + // only really concerned with the meta data for this identity column + var objMeta = meta.idMap[idColumn]; + if (value === null) { + if (objMeta.default !== null && typeof objMeta.default !== 'undefined') { + value = objMeta.default; + } + else { + return; + } + } + // check if object already exists in cache + if (typeof objMeta.cache[value] !== 'undefined') { + if (objMeta.containingIdUsage === null) + return; + // We know for certain that containing column is set if + // containingIdUsage is not null and can cast it as a string + // check and see if this has already been linked to the parent, + // and if so we don't need to continue + var containingId = row[objMeta.containingColumn]; + if (typeof objMeta.containingIdUsage[value] !== 'undefined' + && typeof objMeta.containingIdUsage[value][containingId] !== 'undefined') + return; + // not already placed as to-many relation in container + obj = objMeta.cache[value]; + } + else { + // don't have an object defined for this yet, create it and set the cache + obj = {}; + objMeta.cache[value] = obj; + // copy in properties from table data + for (var k = 0; k < objMeta.valueList.length; k++) { + var cell = objMeta.valueList[k]; + var cellValue = row[cell.column]; + if (cellValue !== null) { + var valueTypeFunction = void 0; + if (isFunction(cell.type)) { + valueTypeFunction = cell.type; + } + else if (typeof cell.type === 'string') { + valueTypeFunction = _this.typeHandlers[cell.type]; + } + if (valueTypeFunction) { + cellValue = valueTypeFunction(cellValue); + } + } + else if (typeof cell.default !== 'undefined') { + cellValue = cell.default; + } + obj[cell.prop] = cellValue; + } + // initialize empty to-many relations, they will be populated when + // those objects build themselves and find this containing object + for (var k = 0; k < objMeta.toManyPropList.length; k++) { + obj[objMeta.toManyPropList[k]] = []; + } + // initialize null to-one relations and then recursively build them + for (var k = 0; k < objMeta.toOneList.length; k++) { + obj[objMeta.toOneList[k].prop] = null; + _nest(row, objMeta.toOneList[k].column); + } + } + // link from the parent + if (objMeta.containingColumn === null) { + // parent is the top level + if (objMeta.isOneOfMany) { + // it is an array + if (_this.struct === null) { + _this.struct = []; + } + _this.struct.push(obj); + } + else { + // it is this object + _this.struct = obj; + } + } + else { + var containingId = row[objMeta.containingColumn]; + var container = meta.idMap[objMeta.containingColumn].cache[containingId]; + // If a container exists, it must not be a root, and thus there should + // be an ownProp set + if (container) { + if (objMeta.isOneOfMany) { + // it is an array + container[objMeta.ownProp].push(obj); + } + else { + // it is this object + container[objMeta.ownProp] = obj; + } + } + // record the containing id so we don't do this again (return in earlier + // part of this method) + var containingIdUsage = objMeta.containingIdUsage; + if (typeof (containingIdUsage)[value] === 'undefined') { + containingIdUsage[value] = {}; + } + containingIdUsage[value][containingId] = true; + } + }; + // struct is populated inside the build function + this.struct = null; + for (var i = 0; i < table.length; i++) { + // go through each row of the table + var row = table[i]; + for (var j = 0; j < meta.primeIdColumnList.length; j++) { + // for each prime id column (corresponding to a to-many relation or + // the top level) attempted to build an object + var primeIdColumn = meta.primeIdColumnList[j]; + _nest(row, primeIdColumn); + } + } + return this.struct; + }; + ; + /* Create a data structure that contains lookups and cache spaces for quick + * reference and action for the workings of the nest method. + */ + NestHydrationJS.prototype.buildMeta = function (structPropToColumnMap) { + var meta; + // internally defines recursive function with extra param. This allows cleaner API + var _buildMeta = function (structPropToColumnMap, isOneOfMany, containingColumn, ownProp) { + var idProp, subIdColumn; + var idProps = []; + var idColumns = []; + var propList = keys(structPropToColumnMap); + if (propList.length === 0) { + throw new Error('invalid structPropToColumnMap format - property \'' + ownProp + '\' can not be an empty array'); + } + for (var i = 0; i < propList.length; i++) { + var prop = propList[i]; + if (structPropToColumnMap[prop].id === true) { + idProp = prop; + idProps.push(prop); + } + } + if (idProp === undefined) { + idProp = propList[0]; + } + // Force we can garuantee that it is a string now, so this will prevent the index error + idProp = idProp; + var idColumn = structPropToColumnMap[idProp].column || structPropToColumnMap[idProp]; + // idColumns = idProps.map(prop => structPropToColumnMap[idProp].column || structPropToColumnMap[idProp]); + if (isOneOfMany) { + meta.primeIdColumnList.push(idColumn); + } + var objMeta = { + valueList: [], + toOneList: [], + toManyPropList: [], + containingColumn: containingColumn, + ownProp: ownProp, + isOneOfMany: isOneOfMany === true, + cache: {}, + containingIdUsage: containingColumn === null ? null : {}, + default: typeof structPropToColumnMap[idProp].default === 'undefined' ? null : structPropToColumnMap[idProp].default + }; + for (var i = 0; i < propList.length; i++) { + var prop = propList[i]; + if (typeof structPropToColumnMap[prop] === 'string') { + // value property + objMeta.valueList.push({ + prop: prop, + column: structPropToColumnMap[prop], + type: undefined, + default: undefined + }); + } + else if (structPropToColumnMap[prop].column) { + // value property + var definitionColumn = structPropToColumnMap[prop]; + objMeta.valueList.push({ + prop: prop, + column: definitionColumn.column, + type: definitionColumn.type, + default: definitionColumn.default + }); + } + else if (isArray(structPropToColumnMap[prop])) { + // list of objects / to-many relation + objMeta.toManyPropList.push(prop); + _buildMeta(structPropToColumnMap[prop][0], true, idColumn, prop); + } + else if (isPlainObject(structPropToColumnMap[prop])) { + // object / to-one relation + var subIdColumn_1 = values(structPropToColumnMap[prop])[0]; + if (typeof subIdColumn_1 === 'undefined') { + throw new Error('invalid structPropToColumnMap format - property \'' + prop + '\' can not be an empty object'); + } + if (subIdColumn_1.column) { + subIdColumn_1 = subIdColumn_1.column; + } + objMeta.toOneList.push({ + prop: prop, + column: subIdColumn_1 + }); + _buildMeta(structPropToColumnMap[prop], false, idColumn, prop); + } + else { + throw new Error('invalid structPropToColumnMap format - property \'' + prop + '\' must be either a string, a plain object or an array'); + } + } + meta.idMap[idColumn] = objMeta; + }; + // this data structure is populated by the _buildMeta function + meta = { + primeIdColumnList: [], + idMap: {} + }; + if (isArray(structPropToColumnMap)) { + if (structPropToColumnMap.length !== 1) { + throw new Error('invalid structPropToColumnMap format - can not have multiple roots for structPropToColumnMap, if an array it must only have one item'); + } + // call with first object, but inform _buildMeta it is an array + _buildMeta(structPropToColumnMap[0], true, null, null); + } + else if (isPlainObject(structPropToColumnMap)) { + // register first column as prime id column + var primeIdColumn = values(structPropToColumnMap)[0]; + if (typeof primeIdColumn === 'undefined') { + throw new Error('invalid structPropToColumnMap format - the base object can not be an empty object'); + } + if (typeof primeIdColumn !== 'string') { + primeIdColumn = primeIdColumn.column; + } + meta.primeIdColumnList.push(primeIdColumn); + // construct the rest + _buildMeta(structPropToColumnMap, false, null, null); + } + return meta; + }; + ; + /* Returns a property mapping data structure based on the names of columns + * in columnList. Used internally by nest when its propertyMapping param + * is not specified. + */ + // structPropToColumnMapFromColumnHints(columnList, renameMapping) { + // var propertyMapping, prop, columnType, type, isId, column, pointer, navList, nav, renamedColumn, prevKeyList; + // if (typeof renameMapping === 'undefined') { + // renameMapping = {}; + // } + // propertyMapping = {base: null}; + // for (let i = 0; i < columnList.length; i++) { + // column = columnList[i]; + // columnType = column.split('___'); + // type = null; + // isId = false; + // for (let j = 1; j < columnType.length; j++) { + // if (columnType[j] === 'ID') { + // isId = true; + // } else if (typeof NestHydrationJS.typeHandlers[columnType[j]] !== 'undefined') { + // type = columnType[j]; + // } + // } + // pointer = propertyMapping; // point to base on each new column + // prop = 'base'; + // navList = columnType[0].split('_'); + // for (let j = 0; j < navList.length; j++) { + // nav = navList[j]; + // if (nav === '') { + // if (pointer[prop] === null) { + // pointer[prop] = [null]; + // } + // pointer = pointer[prop]; + // prop = 0; + // } else { + // if (pointer[prop] === null) { + // pointer[prop] = {}; + // } + // if (typeof pointer[prop][nav] === 'undefined') { + // renamedColumn = typeof renameMapping[column] === 'undefined' + // ? column + // : renameMapping[column] + // ; + // if (type !== null || isId) { + // // no longer a simple mapping, has need of the type or id properties + // renamedColumn = {column: renamedColumn}; + // } + // if (type !== null) { + // // detail the type in the column map if type provided + // renamedColumn.type = type; + // } + // if (isId) { + // // set the id property in the column map + // renamedColumn.id = true; + // // check for any existing id keys that are conflicting + // prevKeyList = keys(pointer[prop]); + // for (let k = 0; k < prevKeyList.length; k++) { + // if (pointer[prop][prevKeyList[k]].id === true) { + // return 'invalid - multiple id - ' + pointer[prop][prevKeyList[k]].column + ' and ' + renamedColumn.column + ' conflict'; + // } + // } + // } + // pointer[prop][nav] = j === (navList.length - 1) + // ? renamedColumn // is leaf node, store full column string + // : null // iteration will replace with object or array + // ; + // } + // pointer = pointer[prop]; + // prop = nav; + // } + // } + // } + // return propertyMapping.base; + // }; + /* Registers a custom type handler */ + NestHydrationJS.prototype.registerType = function (name, handler) { + if (this.typeHandlers[name]) { + throw new Error('Handler with type, ' + name + ', already exists'); + } + this.typeHandlers[name] = handler; + }; + ; + return NestHydrationJS; + }()); + NestHydrationJS_1.NestHydrationJS = NestHydrationJS; +})(NestHydrationJS || (NestHydrationJS = {})); +// We have to wrap this in a function for backwards compatablity +function default_1() { return new NestHydrationJS.NestHydrationJS(); } +exports.default = default_1; +; diff --git a/src/NestHydrationJS.ts b/src/NestHydrationJS.ts new file mode 100644 index 0000000..55057a2 --- /dev/null +++ b/src/NestHydrationJS.ts @@ -0,0 +1,509 @@ +'use strict'; + +const isArray = require('lodash.isarray'); +const isFunction = require('lodash.isfunction'); +const keys = require('lodash.keys'); +const values = require('lodash.values'); +const isPlainObject = require('lodash.isplainobject'); + +module NestHydrationJS { + interface TypeHandlers { + [index: string]: TypeHandler + } + + interface TypeHandler { + (cellValue: any): any + } + + interface MetaValueProps { + prop: string, + column: string, + type?: string | TypeHandler, + default?: any + } + + interface Dictionary { + [index: string]: TValue; + } + + interface MetaColumnData { + valueList: Array, + toOneList: Array, + toManyPropList: Array, + containingColumn: string | null, + ownProp: string | null, + isOneOfMany: boolean, + cache: Dictionary, + containingIdUsage: Dictionary> | null, + default?: any + } + + interface MetaData { + primeIdColumnList: Array, + idMap: { [index: string]: MetaColumnData } + } + + interface DefinitionColumn { + column: string, + id?: boolean, + default?: any, + type?: string + } + + interface Definition { + [index: string]: DefinitionColumn | string | Definition | Definition[] + } + + interface Data { + [index: string]: any, + [index: number]: any + } + + export class NestHydrationJS { + + typeHandlers = { + NUMBER: function (cellValue: any) { + return parseFloat(cellValue); + }, + BOOLEAN: function (cellValue: any) { + return cellValue == true; + } + } as TypeHandlers; + + struct: object | Array | null = null + + /* Creates a data structure containing nested objects and/or arrays from + * tabular data based on a structure definition provided by + * structPropToColumnMap. If structPropToColumnMap is not provided but + * the data has column names that follow a particular convention then a + * nested structures can also be created. + */ + nest(data: any, structPropToColumnMap: Definition | Definition[] | null | boolean): any { + + let table; + + // VALIDATE PARAMS AND BASIC INITIALIZATION + + // Determines that on no results, and empty list is used instead of null. // NOTE: fact check this + let listOnEmpty = false; + + if (typeof structPropToColumnMap === 'undefined') { + structPropToColumnMap = null; + } + + if (data === null) { + return null; + } + + if (!isArray(structPropToColumnMap) && !isPlainObject(structPropToColumnMap) && structPropToColumnMap !== null && structPropToColumnMap !== true) { + throw new Error('nest expects param structPropToColumnMap to be an array, plain object, null, or true'); + } + + if (isPlainObject(data)) { + // internal table should be a table format but a plain object + // could be passed as the first (and only) row of that table + table = [data]; + } else if (isArray(data)) { + table = data; + } else { + throw Error('nest expects param data to be in the form of a plain object or an array of plain objects (forming a table)'); + } + + // structPropToColumnMap can be set to true as a tie break between + // returning null (empty structure) or an empty list + if (structPropToColumnMap === true) { + listOnEmpty = true; + structPropToColumnMap = null; + } + + if (structPropToColumnMap === null && table.length > 0) { + // property mapping not specified, determine it from column names + // structPropToColumnMap = this.structPropToColumnMapFromColumnHints(keys(table[0])); + } + + if (structPropToColumnMap === null) { + // properties is empty, can't form structure or determine content + // for a list. Assume a structure unless listOnEmpty + return listOnEmpty ? [] : null; + } else if (table.length === 0) { + // table is empty, return the appropriate empty result based on input definition + return isArray(structPropToColumnMap) ? [] : null; + } + + // COMPLETE VALIDATING PARAMS AND BASIC INITIALIZATION + + let meta = this.buildMeta(structPropToColumnMap); + // BUILD FROM TABLE + + // defines function that can be called recursively + let _nest = (row: Dictionary, idColumn: string) => { + + // Obj is the actual object that will end up in the final structure + let obj: Data; + + // Value here is really the id + let value: any = row[idColumn]; + + // only really concerned with the meta data for this identity column + let objMeta = meta.idMap[idColumn]; + + if (value === null) { + if (objMeta.default !== null && typeof objMeta.default !== 'undefined') { + value = objMeta.default; + } else { + return; + } + } + + // check if object already exists in cache + if (typeof objMeta.cache[value] !== 'undefined') { + + if (objMeta.containingIdUsage === null) return; + + // We know for certain that containing column is set if + // containingIdUsage is not null and can cast it as a string + + // check and see if this has already been linked to the parent, + // and if so we don't need to continue + let containingId = row[objMeta.containingColumn]; + if (typeof objMeta.containingIdUsage[value] !== 'undefined' + && typeof objMeta.containingIdUsage[value][containingId] !== 'undefined' + ) return; + + // not already placed as to-many relation in container + obj = objMeta.cache[value]; + } else { + // don't have an object defined for this yet, create it and set the cache + obj = {}; + objMeta.cache[value] = obj; + + // copy in properties from table data + for (let k = 0; k < objMeta.valueList.length; k++) { + let cell = objMeta.valueList[k]; + let cellValue = row[cell.column]; + if (cellValue !== null) { + let valueTypeFunction: TypeHandler | undefined; + + if (isFunction(cell.type)) { + valueTypeFunction = cell.type as TypeHandler; + } else if (typeof cell.type === 'string') { + valueTypeFunction = this.typeHandlers[cell.type]; + } + + if (valueTypeFunction) { + cellValue = valueTypeFunction(cellValue); + } + } else if (typeof cell.default !== 'undefined') { + cellValue = cell.default; + } + + obj[cell.prop] = cellValue; + } + + // initialize empty to-many relations, they will be populated when + // those objects build themselves and find this containing object + for (let k = 0; k < objMeta.toManyPropList.length; k++) { + obj[objMeta.toManyPropList[k]] = []; + } + + // initialize null to-one relations and then recursively build them + for (let k = 0; k < objMeta.toOneList.length; k++) { + obj[objMeta.toOneList[k].prop] = null; + _nest(row, objMeta.toOneList[k].column); + } + } + + // link from the parent + if (objMeta.containingColumn === null) { + // parent is the top level + if (objMeta.isOneOfMany) { + // it is an array + if (this.struct === null) { + this.struct = []; + } + (>this.struct).push(obj); + } else { + // it is this object + this.struct = obj; + } + } else { + let containingId = row[objMeta.containingColumn]; + let container = meta.idMap[objMeta.containingColumn].cache[containingId]; + + // If a container exists, it must not be a root, and thus there should + // be an ownProp set + if (container) { + if (objMeta.isOneOfMany) { + // it is an array + container[objMeta.ownProp].push(obj); + } else { + // it is this object + container[objMeta.ownProp] = obj; + } + } + + // record the containing id so we don't do this again (return in earlier + // part of this method) + const containingIdUsage = >>objMeta.containingIdUsage + if (typeof (containingIdUsage)[value] === 'undefined') { + containingIdUsage[value] = {}; + } + containingIdUsage[value][containingId] = true; + } + }; + + // struct is populated inside the build function + this.struct = null; + + for (let i = 0; i < table.length; i++) { + // go through each row of the table + let row = table[i]; + + for (let j = 0; j < meta.primeIdColumnList.length; j++) { + // for each prime id column (corresponding to a to-many relation or + // the top level) attempted to build an object + let primeIdColumn = meta.primeIdColumnList[j]; + + _nest(row, primeIdColumn); + } + } + + return this.struct; + }; + + /* Create a data structure that contains lookups and cache spaces for quick + * reference and action for the workings of the nest method. + */ + buildMeta(structPropToColumnMap: Definition | Definition[]): MetaData { + + var meta: MetaData; + + // internally defines recursive function with extra param. This allows cleaner API + let _buildMeta = function( + structPropToColumnMap: Definition, + isOneOfMany: boolean, + containingColumn: string | null, + ownProp: string | null) + { + var idProp: string | undefined, subIdColumn; + + let idProps = []; + let idColumns = []; + + let propList = keys(structPropToColumnMap); + if (propList.length === 0) { + throw new Error('invalid structPropToColumnMap format - property \'' + ownProp + '\' can not be an empty array'); + } + + for (let i = 0; i < propList.length; i++) { + let prop = propList[i]; + if ((structPropToColumnMap[prop]).id === true) { + idProp = prop; + idProps.push(prop); + } + } + + if (idProp === undefined) { + idProp = propList[0]; + } + + // Force we can garuantee that it is a string now, so this will prevent the index error + idProp = idProp as string + + let idColumn = (structPropToColumnMap[idProp]).column || structPropToColumnMap[idProp] as string; + // idColumns = idProps.map(prop => structPropToColumnMap[idProp].column || structPropToColumnMap[idProp]); + + if (isOneOfMany) { + meta.primeIdColumnList.push(idColumn); + } + + let objMeta: MetaColumnData = { + valueList: [], + toOneList: [], + toManyPropList: [], + containingColumn: containingColumn, + ownProp: ownProp, + isOneOfMany: isOneOfMany === true, + cache: {}, + containingIdUsage: containingColumn === null ? null : {}, + default: typeof (structPropToColumnMap[idProp]).default === 'undefined' ? null : (structPropToColumnMap[idProp]).default + }; + + for (let i = 0; i < propList.length; i++) { + let prop = propList[i]; + if (typeof structPropToColumnMap[prop] === 'string') { + // value property + objMeta.valueList.push({ + prop: prop, + column: structPropToColumnMap[prop] as string, + type: undefined, + default: undefined + }); + } else if ((structPropToColumnMap[prop]).column) { + // value property + const definitionColumn = structPropToColumnMap[prop] + objMeta.valueList.push({ + prop: prop, + column: definitionColumn.column, + type: definitionColumn.type, + default: definitionColumn.default + }); + } else if (isArray(structPropToColumnMap[prop])) { + // list of objects / to-many relation + objMeta.toManyPropList.push(prop); + + _buildMeta((>structPropToColumnMap[prop])[0], true, idColumn, prop); + } else if (isPlainObject(structPropToColumnMap[prop])) { + // object / to-one relation + + let subIdColumn = values(structPropToColumnMap[prop])[0]; + if (typeof subIdColumn === 'undefined') { + throw new Error('invalid structPropToColumnMap format - property \'' + prop + '\' can not be an empty object'); + } + + if (subIdColumn.column) { + subIdColumn = subIdColumn.column; + } + + objMeta.toOneList.push({ + prop: prop, + column: subIdColumn + }); + _buildMeta(structPropToColumnMap[prop], false, idColumn, prop); + } else { + throw new Error('invalid structPropToColumnMap format - property \'' + prop + '\' must be either a string, a plain object or an array'); + } + } + + meta.idMap[idColumn] = objMeta; + }; + + // this data structure is populated by the _buildMeta function + meta = { + primeIdColumnList: [], + idMap: {} + } as MetaData; + + if (isArray(structPropToColumnMap)) { + if (structPropToColumnMap.length !== 1) { + throw new Error('invalid structPropToColumnMap format - can not have multiple roots for structPropToColumnMap, if an array it must only have one item'); + } + // call with first object, but inform _buildMeta it is an array + _buildMeta((>structPropToColumnMap)[0], true, null, null); + } else if (isPlainObject(structPropToColumnMap)) { + // register first column as prime id column + let primeIdColumn = values(structPropToColumnMap)[0]; + if (typeof primeIdColumn === 'undefined') { + throw new Error('invalid structPropToColumnMap format - the base object can not be an empty object'); + } + + if (typeof primeIdColumn !== 'string') { + primeIdColumn = primeIdColumn.column; + } + + meta.primeIdColumnList.push(primeIdColumn); + + // construct the rest + _buildMeta(structPropToColumnMap, false, null, null); + } + return meta; + }; + + /* Returns a property mapping data structure based on the names of columns + * in columnList. Used internally by nest when its propertyMapping param + * is not specified. + */ + // structPropToColumnMapFromColumnHints(columnList, renameMapping) { + // var propertyMapping, prop, columnType, type, isId, column, pointer, navList, nav, renamedColumn, prevKeyList; + + // if (typeof renameMapping === 'undefined') { + // renameMapping = {}; + // } + + // propertyMapping = {base: null}; + + // for (let i = 0; i < columnList.length; i++) { + // column = columnList[i]; + + // columnType = column.split('___'); + + // type = null; + // isId = false; + // for (let j = 1; j < columnType.length; j++) { + // if (columnType[j] === 'ID') { + // isId = true; + // } else if (typeof NestHydrationJS.typeHandlers[columnType[j]] !== 'undefined') { + // type = columnType[j]; + // } + // } + + // pointer = propertyMapping; // point to base on each new column + // prop = 'base'; + + // navList = columnType[0].split('_'); + + // for (let j = 0; j < navList.length; j++) { + // nav = navList[j]; + + // if (nav === '') { + // if (pointer[prop] === null) { + // pointer[prop] = [null]; + // } + // pointer = pointer[prop]; + // prop = 0; + // } else { + // if (pointer[prop] === null) { + // pointer[prop] = {}; + // } + // if (typeof pointer[prop][nav] === 'undefined') { + // renamedColumn = typeof renameMapping[column] === 'undefined' + // ? column + // : renameMapping[column] + // ; + // if (type !== null || isId) { + // // no longer a simple mapping, has need of the type or id properties + // renamedColumn = {column: renamedColumn}; + // } + // if (type !== null) { + // // detail the type in the column map if type provided + // renamedColumn.type = type; + // } + // if (isId) { + // // set the id property in the column map + // renamedColumn.id = true; + // // check for any existing id keys that are conflicting + // prevKeyList = keys(pointer[prop]); + // for (let k = 0; k < prevKeyList.length; k++) { + // if (pointer[prop][prevKeyList[k]].id === true) { + // return 'invalid - multiple id - ' + pointer[prop][prevKeyList[k]].column + ' and ' + renamedColumn.column + ' conflict'; + // } + // } + // } + // pointer[prop][nav] = j === (navList.length - 1) + // ? renamedColumn // is leaf node, store full column string + // : null // iteration will replace with object or array + // ; + // } + // pointer = pointer[prop]; + // prop = nav; + // } + // } + // } + + // return propertyMapping.base; + // }; + + /* Registers a custom type handler */ + registerType(name: string, handler: TypeHandler) { + if (this.typeHandlers[name]) { + throw new Error('Handler with type, ' + name + ', already exists'); + } + + this.typeHandlers[name] = handler; + }; + } + +} + +// We have to wrap this in a function for backwards compatablity +export default function(): NestHydrationJS.NestHydrationJS { return new NestHydrationJS.NestHydrationJS() }; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..04d97b8 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "declaration": true, + "outDir": "./lib", + "strict": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules/", "spec"] +} From 0ae9bd007c61ccadf7682732eb359054ef698181 Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Wed, 6 Mar 2019 15:48:32 -0800 Subject: [PATCH 04/25] fixed package.json issues --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 76f3e60..47f1953 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "Nested" ], "license": "ISC", - "main": "NestHydrationJS.js", + "main": "lib/NestHydrationJS.js", + "types": "lib/NestHydrationJS.d.js", "scripts": { "coverage": "nyc jasmine", "coveralls": "nyc report --reporter=text-lcov | coveralls", From a390d7522abcda3d89366d5c13f21862218d497f Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Wed, 6 Mar 2019 15:52:57 -0800 Subject: [PATCH 05/25] update export function --- lib/NestHydrationJS.d.ts | 7 +++++-- lib/NestHydrationJS.js | 5 +---- src/NestHydrationJS.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/NestHydrationJS.d.ts b/lib/NestHydrationJS.d.ts index 71930e2..39b6215 100644 --- a/lib/NestHydrationJS.d.ts +++ b/lib/NestHydrationJS.d.ts @@ -1,3 +1,8 @@ +declare const isArray: any; +declare const isFunction: any; +declare const keys: any; +declare const values: any; +declare const isPlainObject: any; declare module NestHydrationJS { interface TypeHandlers { [index: string]: TypeHandler; @@ -48,5 +53,3 @@ declare module NestHydrationJS { registerType(name: string, handler: TypeHandler): void; } } -export default function (): NestHydrationJS.NestHydrationJS; -export {}; diff --git a/lib/NestHydrationJS.js b/lib/NestHydrationJS.js index 064c646..6b18994 100644 --- a/lib/NestHydrationJS.js +++ b/lib/NestHydrationJS.js @@ -1,5 +1,4 @@ 'use strict'; -Object.defineProperty(exports, "__esModule", { value: true }); var isArray = require('lodash.isarray'); var isFunction = require('lodash.isfunction'); var keys = require('lodash.keys'); @@ -397,6 +396,4 @@ var NestHydrationJS; NestHydrationJS_1.NestHydrationJS = NestHydrationJS; })(NestHydrationJS || (NestHydrationJS = {})); // We have to wrap this in a function for backwards compatablity -function default_1() { return new NestHydrationJS.NestHydrationJS(); } -exports.default = default_1; -; +module.exports = function () { return new NestHydrationJS.NestHydrationJS(); }; diff --git a/src/NestHydrationJS.ts b/src/NestHydrationJS.ts index 55057a2..f564ef6 100644 --- a/src/NestHydrationJS.ts +++ b/src/NestHydrationJS.ts @@ -506,4 +506,4 @@ module NestHydrationJS { } // We have to wrap this in a function for backwards compatablity -export default function(): NestHydrationJS.NestHydrationJS { return new NestHydrationJS.NestHydrationJS() }; +module.exports = function(): NestHydrationJS.NestHydrationJS { return new NestHydrationJS.NestHydrationJS() }; From 1cfb556805989d26b63ca9a591b55401e068ed04 Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Wed, 6 Mar 2019 17:12:30 -0800 Subject: [PATCH 06/25] fix exports --- lib/NestHydrationJS.d.ts | 7 +--- lib/NestHydrationJS.js | 3 +- lib/test.d.ts | 0 lib/test.js | 81 +++++++++++++++++++++++++++++++++++++++ src/NestHydrationJS.ts | 2 +- src/test.ts | 83 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 168 insertions(+), 8 deletions(-) create mode 100644 lib/test.d.ts create mode 100644 lib/test.js create mode 100644 src/test.ts diff --git a/lib/NestHydrationJS.d.ts b/lib/NestHydrationJS.d.ts index 39b6215..52193d1 100644 --- a/lib/NestHydrationJS.d.ts +++ b/lib/NestHydrationJS.d.ts @@ -1,8 +1,3 @@ -declare const isArray: any; -declare const isFunction: any; -declare const keys: any; -declare const values: any; -declare const isPlainObject: any; declare module NestHydrationJS { interface TypeHandlers { [index: string]: TypeHandler; @@ -53,3 +48,5 @@ declare module NestHydrationJS { registerType(name: string, handler: TypeHandler): void; } } +declare const _default: () => NestHydrationJS.NestHydrationJS; +export = _default; diff --git a/lib/NestHydrationJS.js b/lib/NestHydrationJS.js index 6b18994..b251b69 100644 --- a/lib/NestHydrationJS.js +++ b/lib/NestHydrationJS.js @@ -395,5 +395,4 @@ var NestHydrationJS; }()); NestHydrationJS_1.NestHydrationJS = NestHydrationJS; })(NestHydrationJS || (NestHydrationJS = {})); -// We have to wrap this in a function for backwards compatablity -module.exports = function () { return new NestHydrationJS.NestHydrationJS(); }; +module.exports = function generate() { return new NestHydrationJS.NestHydrationJS(); }; diff --git a/lib/test.d.ts b/lib/test.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/lib/test.js b/lib/test.js new file mode 100644 index 0000000..03dcc0f --- /dev/null +++ b/lib/test.js @@ -0,0 +1,81 @@ +"use strict"; +// var hydration = require('./lib/NestHydrationJS')(); +// let hydrate = hydration(); +// const data = [{ +// 'group_has_service.service_id': 2, +// 'group_has_service.group_id': 5, +// 'zone.id': 5, +// 'zone.name': 'Wings', +// 'service.id': 2, +// 'service.name': 'Deep Clean', +// 'group.id': 5, +// 'group.name': 'Group 5!' +// }, +// { +// 'group_has_service.service_id': 2, +// 'group_has_service.group_id': 5, +// 'zone.id': 4, +// 'zone.name': 'Struts', +// 'service.id': 2, +// 'service.name': 'Deep Clean', +// 'group.id': 5, +// 'group.name': 'Group 5!' +// }, +// { +// 'group_has_service.service_id': 2, +// 'group_has_service.group_id': 5, +// 'zone.id': 3, +// 'zone.name': 'Wheels', +// 'service.id': 2, +// 'service.name': 'Deep Clean', +// 'group.id': 5, +// 'group.name': 'Group 5!' +// }, +// { +// 'group_has_service.service_id': 3, +// 'group_has_service.group_id': 5, +// 'zone.id': 3, +// 'zone.name': 'Wheels', +// 'service.id': 3, +// 'service.name': 'Quick Wash', +// 'group.id': 5, +// 'group.name': 'Group 5!' +// }, +// { +// 'group_has_service.service_id': 3, +// 'group_has_service.group_id': 5, +// 'zone.id': 1, +// 'zone.name': 'Elevators', +// 'service.id': 3, +// 'service.name': 'Quick Wash', +// 'group.id': 5, +// 'group.name': 'Group 5!' +// }, +// { +// 'group_has_service.service_id': 3, +// 'group_has_service.group_id': 5, +// 'zone.id': 6, +// 'zone.name': 'APU', +// 'service.id': 3, +// 'service.name': 'Quick Wash', +// 'group.id': 5, +// 'group.name': 'Group 5!' +// },] +// const description = [{ +// service_id: {column: 'group_has_service.service_id', id: true}, +// group_id: {column: 'group_has_service.group_id'}, +// service: { +// id: 'service.id', +// name: 'service.name', +// zone: [{ +// id: 'zone.id', +// name: 'zone.name' +// }] +// }, +// group: { +// id: 'group.id', +// name: 'group.name' +// } +// }] +// const res = hydrate.nest(data, description); +// console.log(res); diff --git a/src/NestHydrationJS.ts b/src/NestHydrationJS.ts index f564ef6..584ff69 100644 --- a/src/NestHydrationJS.ts +++ b/src/NestHydrationJS.ts @@ -506,4 +506,4 @@ module NestHydrationJS { } // We have to wrap this in a function for backwards compatablity -module.exports = function(): NestHydrationJS.NestHydrationJS { return new NestHydrationJS.NestHydrationJS() }; +export = function generate(): NestHydrationJS.NestHydrationJS { return new NestHydrationJS.NestHydrationJS() }; diff --git a/src/test.ts b/src/test.ts new file mode 100644 index 0000000..453d5fa --- /dev/null +++ b/src/test.ts @@ -0,0 +1,83 @@ +// var hydration = require('./lib/NestHydrationJS')(); +// let hydrate = hydration(); + +// const data = [{ +// 'group_has_service.service_id': 2, +// 'group_has_service.group_id': 5, +// 'zone.id': 5, +// 'zone.name': 'Wings', +// 'service.id': 2, +// 'service.name': 'Deep Clean', +// 'group.id': 5, +// 'group.name': 'Group 5!' +// }, +// { +// 'group_has_service.service_id': 2, +// 'group_has_service.group_id': 5, +// 'zone.id': 4, +// 'zone.name': 'Struts', +// 'service.id': 2, +// 'service.name': 'Deep Clean', +// 'group.id': 5, +// 'group.name': 'Group 5!' +// }, +// { +// 'group_has_service.service_id': 2, +// 'group_has_service.group_id': 5, +// 'zone.id': 3, +// 'zone.name': 'Wheels', +// 'service.id': 2, +// 'service.name': 'Deep Clean', +// 'group.id': 5, +// 'group.name': 'Group 5!' +// }, +// { +// 'group_has_service.service_id': 3, +// 'group_has_service.group_id': 5, +// 'zone.id': 3, +// 'zone.name': 'Wheels', +// 'service.id': 3, +// 'service.name': 'Quick Wash', +// 'group.id': 5, +// 'group.name': 'Group 5!' +// }, +// { +// 'group_has_service.service_id': 3, +// 'group_has_service.group_id': 5, +// 'zone.id': 1, +// 'zone.name': 'Elevators', +// 'service.id': 3, +// 'service.name': 'Quick Wash', +// 'group.id': 5, +// 'group.name': 'Group 5!' +// }, +// { +// 'group_has_service.service_id': 3, +// 'group_has_service.group_id': 5, +// 'zone.id': 6, +// 'zone.name': 'APU', +// 'service.id': 3, +// 'service.name': 'Quick Wash', +// 'group.id': 5, +// 'group.name': 'Group 5!' +// },] + +// const description = [{ +// service_id: {column: 'group_has_service.service_id', id: true}, +// group_id: {column: 'group_has_service.group_id'}, +// service: { +// id: 'service.id', +// name: 'service.name', +// zone: [{ +// id: 'zone.id', +// name: 'zone.name' +// }] +// }, +// group: { +// id: 'group.id', +// name: 'group.name' +// } +// }] + +// const res = hydrate.nest(data, description); +// console.log(res); \ No newline at end of file From ec13042a1ffc483ddb9d43dc40583292cbd6bc8a Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Wed, 6 Mar 2019 17:15:22 -0800 Subject: [PATCH 07/25] add private methods to class --- lib/NestHydrationJS.d.ts | 35 +++-------------------------------- src/NestHydrationJS.ts | 6 +++--- 2 files changed, 6 insertions(+), 35 deletions(-) diff --git a/lib/NestHydrationJS.d.ts b/lib/NestHydrationJS.d.ts index 52193d1..82f7493 100644 --- a/lib/NestHydrationJS.d.ts +++ b/lib/NestHydrationJS.d.ts @@ -1,36 +1,7 @@ declare module NestHydrationJS { - interface TypeHandlers { - [index: string]: TypeHandler; - } interface TypeHandler { (cellValue: any): any; } - interface MetaValueProps { - prop: string; - column: string; - type?: string | TypeHandler; - default?: any; - } - interface Dictionary { - [index: string]: TValue; - } - interface MetaColumnData { - valueList: Array; - toOneList: Array; - toManyPropList: Array; - containingColumn: string | null; - ownProp: string | null; - isOneOfMany: boolean; - cache: Dictionary; - containingIdUsage: Dictionary> | null; - default?: any; - } - interface MetaData { - primeIdColumnList: Array; - idMap: { - [index: string]: MetaColumnData; - }; - } interface DefinitionColumn { column: string; id?: boolean; @@ -41,10 +12,10 @@ declare module NestHydrationJS { [index: string]: DefinitionColumn | string | Definition | Definition[]; } class NestHydrationJS { - typeHandlers: TypeHandlers; - struct: object | Array | null; + private typeHandlers; + private struct; nest(data: any, structPropToColumnMap: Definition | Definition[] | null | boolean): any; - buildMeta(structPropToColumnMap: Definition | Definition[]): MetaData; + private buildMeta; registerType(name: string, handler: TypeHandler): void; } } diff --git a/src/NestHydrationJS.ts b/src/NestHydrationJS.ts index 584ff69..34c6791 100644 --- a/src/NestHydrationJS.ts +++ b/src/NestHydrationJS.ts @@ -61,7 +61,7 @@ module NestHydrationJS { export class NestHydrationJS { - typeHandlers = { + private typeHandlers = { NUMBER: function (cellValue: any) { return parseFloat(cellValue); }, @@ -70,7 +70,7 @@ module NestHydrationJS { } } as TypeHandlers; - struct: object | Array | null = null + private struct: object | Array | null = null /* Creates a data structure containing nested objects and/or arrays from * tabular data based on a structure definition provided by @@ -274,7 +274,7 @@ module NestHydrationJS { /* Create a data structure that contains lookups and cache spaces for quick * reference and action for the workings of the nest method. */ - buildMeta(structPropToColumnMap: Definition | Definition[]): MetaData { + private buildMeta(structPropToColumnMap: Definition | Definition[]): MetaData { var meta: MetaData; From 93409bfe63c2883d5a29d6500e9cdf5d0d19caee Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Wed, 6 Mar 2019 17:58:19 -0800 Subject: [PATCH 08/25] finish out typescript conversion --- lib/NestHydrationJS.d.ts | 4 ++ lib/NestHydrationJS.js | 142 ++++++++++++++++++++------------------- src/NestHydrationJS.ts | 140 +++++++++++++++++++------------------- 3 files changed, 146 insertions(+), 140 deletions(-) diff --git a/lib/NestHydrationJS.d.ts b/lib/NestHydrationJS.d.ts index 82f7493..48b7a3e 100644 --- a/lib/NestHydrationJS.d.ts +++ b/lib/NestHydrationJS.d.ts @@ -2,6 +2,9 @@ declare module NestHydrationJS { interface TypeHandler { (cellValue: any): any; } + interface Dictionary { + [index: string]: TValue; + } interface DefinitionColumn { column: string; id?: boolean; @@ -16,6 +19,7 @@ declare module NestHydrationJS { private struct; nest(data: any, structPropToColumnMap: Definition | Definition[] | null | boolean): any; private buildMeta; + structPropToColumnMapFromColumnHints(columnList: Array, renameMapping?: Dictionary): any; registerType(name: string, handler: TypeHandler): void; } } diff --git a/lib/NestHydrationJS.js b/lib/NestHydrationJS.js index b251b69..784f462 100644 --- a/lib/NestHydrationJS.js +++ b/lib/NestHydrationJS.js @@ -58,7 +58,7 @@ var NestHydrationJS; } if (structPropToColumnMap === null && table.length > 0) { // property mapping not specified, determine it from column names - // structPropToColumnMap = this.structPropToColumnMapFromColumnHints(keys(table[0])); + structPropToColumnMap = this.structPropToColumnMapFromColumnHints(keys(table[0])); } if (structPropToColumnMap === null) { // properties is empty, can't form structure or determine content @@ -313,76 +313,78 @@ var NestHydrationJS; /* Returns a property mapping data structure based on the names of columns * in columnList. Used internally by nest when its propertyMapping param * is not specified. + * */ - // structPropToColumnMapFromColumnHints(columnList, renameMapping) { - // var propertyMapping, prop, columnType, type, isId, column, pointer, navList, nav, renamedColumn, prevKeyList; - // if (typeof renameMapping === 'undefined') { - // renameMapping = {}; - // } - // propertyMapping = {base: null}; - // for (let i = 0; i < columnList.length; i++) { - // column = columnList[i]; - // columnType = column.split('___'); - // type = null; - // isId = false; - // for (let j = 1; j < columnType.length; j++) { - // if (columnType[j] === 'ID') { - // isId = true; - // } else if (typeof NestHydrationJS.typeHandlers[columnType[j]] !== 'undefined') { - // type = columnType[j]; - // } - // } - // pointer = propertyMapping; // point to base on each new column - // prop = 'base'; - // navList = columnType[0].split('_'); - // for (let j = 0; j < navList.length; j++) { - // nav = navList[j]; - // if (nav === '') { - // if (pointer[prop] === null) { - // pointer[prop] = [null]; - // } - // pointer = pointer[prop]; - // prop = 0; - // } else { - // if (pointer[prop] === null) { - // pointer[prop] = {}; - // } - // if (typeof pointer[prop][nav] === 'undefined') { - // renamedColumn = typeof renameMapping[column] === 'undefined' - // ? column - // : renameMapping[column] - // ; - // if (type !== null || isId) { - // // no longer a simple mapping, has need of the type or id properties - // renamedColumn = {column: renamedColumn}; - // } - // if (type !== null) { - // // detail the type in the column map if type provided - // renamedColumn.type = type; - // } - // if (isId) { - // // set the id property in the column map - // renamedColumn.id = true; - // // check for any existing id keys that are conflicting - // prevKeyList = keys(pointer[prop]); - // for (let k = 0; k < prevKeyList.length; k++) { - // if (pointer[prop][prevKeyList[k]].id === true) { - // return 'invalid - multiple id - ' + pointer[prop][prevKeyList[k]].column + ' and ' + renamedColumn.column + ' conflict'; - // } - // } - // } - // pointer[prop][nav] = j === (navList.length - 1) - // ? renamedColumn // is leaf node, store full column string - // : null // iteration will replace with object or array - // ; - // } - // pointer = pointer[prop]; - // prop = nav; - // } - // } - // } - // return propertyMapping.base; - // }; + NestHydrationJS.prototype.structPropToColumnMapFromColumnHints = function (columnList, renameMapping) { + if (typeof renameMapping === 'undefined') { + renameMapping = {}; + } + var propertyMapping = { base: null }; + for (var i = 0; i < columnList.length; i++) { + var column = columnList[i]; + var columnType = column.split('___'); + var type = null; + var isId = false; + for (var j = 1; j < columnType.length; j++) { + if (columnType[j] === 'ID') { + isId = true; + } + else if (typeof this.typeHandlers[columnType[j]] !== 'undefined') { + type = columnType[j]; + } + } + var pointer = propertyMapping; // point to base on each new column + var prop = 'base'; + var navList = columnType[0].split('_'); + for (var j = 0; j < navList.length; j++) { + var nav = navList[j]; + if (nav === '') { + if (pointer[prop] === null) { + pointer[prop] = [null]; + } + pointer = pointer[prop]; + prop = 0; + } + else { + if (pointer[prop] === null) { + pointer[prop] = {}; + } + if (typeof pointer[prop][nav] === 'undefined') { + var renamedColumn = typeof renameMapping[column] === 'undefined' + ? column + : renameMapping[column]; + if (type !== null || isId) { + // no longer a simple mapping, has need of the type or id properties + renamedColumn = { column: renamedColumn }; + } + if (type !== null) { + // detail the type in the column map if type provided + renamedColumn.type = type; + } + if (isId) { + // set the id property in the column map + renamedColumn.id = true; + // check for any existing id keys that are conflicting + var prevKeyList = keys(pointer[prop]); + for (var k = 0; k < prevKeyList.length; k++) { + if (pointer[prop][prevKeyList[k]].id === true) { + return 'invalid - multiple id - ' + pointer[prop][prevKeyList[k]].column + ' and ' + renamedColumn.column + ' conflict'; + } + } + } + pointer[prop][nav] = j === (navList.length - 1) + ? renamedColumn // is leaf node, store full column string + : null // iteration will replace with object or array + ; + } + pointer = pointer[prop]; + prop = nav; + } + } + } + return propertyMapping.base; + }; + ; /* Registers a custom type handler */ NestHydrationJS.prototype.registerType = function (name, handler) { if (this.typeHandlers[name]) { diff --git a/src/NestHydrationJS.ts b/src/NestHydrationJS.ts index 34c6791..225fc2d 100644 --- a/src/NestHydrationJS.ts +++ b/src/NestHydrationJS.ts @@ -118,7 +118,7 @@ module NestHydrationJS { if (structPropToColumnMap === null && table.length > 0) { // property mapping not specified, determine it from column names - // structPropToColumnMap = this.structPropToColumnMapFromColumnHints(keys(table[0])); + structPropToColumnMap = this.structPropToColumnMapFromColumnHints(keys(table[0])); } if (structPropToColumnMap === null) { @@ -412,86 +412,86 @@ module NestHydrationJS { /* Returns a property mapping data structure based on the names of columns * in columnList. Used internally by nest when its propertyMapping param * is not specified. + * */ - // structPropToColumnMapFromColumnHints(columnList, renameMapping) { - // var propertyMapping, prop, columnType, type, isId, column, pointer, navList, nav, renamedColumn, prevKeyList; + structPropToColumnMapFromColumnHints(columnList: Array, renameMapping?: Dictionary) { - // if (typeof renameMapping === 'undefined') { - // renameMapping = {}; - // } + if (typeof renameMapping === 'undefined') { + renameMapping = {}; + } - // propertyMapping = {base: null}; + let propertyMapping: any = {base: null}; - // for (let i = 0; i < columnList.length; i++) { - // column = columnList[i]; + for (let i = 0; i < columnList.length; i++) { + let column = columnList[i]; - // columnType = column.split('___'); + let columnType = column.split('___'); - // type = null; - // isId = false; - // for (let j = 1; j < columnType.length; j++) { - // if (columnType[j] === 'ID') { - // isId = true; - // } else if (typeof NestHydrationJS.typeHandlers[columnType[j]] !== 'undefined') { - // type = columnType[j]; - // } - // } + let type = null; + let isId = false; + for (let j = 1; j < columnType.length; j++) { + if (columnType[j] === 'ID') { + isId = true; + } else if (typeof this.typeHandlers[columnType[j]] !== 'undefined') { + type = columnType[j]; + } + } - // pointer = propertyMapping; // point to base on each new column - // prop = 'base'; + let pointer = propertyMapping; // point to base on each new column + let prop: string | number = 'base'; - // navList = columnType[0].split('_'); + let navList = columnType[0].split('_'); - // for (let j = 0; j < navList.length; j++) { - // nav = navList[j]; + for (let j = 0; j < navList.length; j++) { + let nav = navList[j]; - // if (nav === '') { - // if (pointer[prop] === null) { - // pointer[prop] = [null]; - // } - // pointer = pointer[prop]; - // prop = 0; - // } else { - // if (pointer[prop] === null) { - // pointer[prop] = {}; - // } - // if (typeof pointer[prop][nav] === 'undefined') { - // renamedColumn = typeof renameMapping[column] === 'undefined' - // ? column - // : renameMapping[column] - // ; - // if (type !== null || isId) { - // // no longer a simple mapping, has need of the type or id properties - // renamedColumn = {column: renamedColumn}; - // } - // if (type !== null) { - // // detail the type in the column map if type provided - // renamedColumn.type = type; - // } - // if (isId) { - // // set the id property in the column map - // renamedColumn.id = true; - // // check for any existing id keys that are conflicting - // prevKeyList = keys(pointer[prop]); - // for (let k = 0; k < prevKeyList.length; k++) { - // if (pointer[prop][prevKeyList[k]].id === true) { - // return 'invalid - multiple id - ' + pointer[prop][prevKeyList[k]].column + ' and ' + renamedColumn.column + ' conflict'; - // } - // } - // } - // pointer[prop][nav] = j === (navList.length - 1) - // ? renamedColumn // is leaf node, store full column string - // : null // iteration will replace with object or array - // ; - // } - // pointer = pointer[prop]; - // prop = nav; - // } - // } - // } + if (nav === '') { + if (pointer[prop] === null) { + pointer[prop] = [null]; + } + pointer = pointer[prop]; + prop = 0; + } else { + if (pointer[prop] === null) { + pointer[prop] = {}; + } + if (typeof pointer[prop][nav] === 'undefined') { + let renamedColumn: any = typeof renameMapping[column] === 'undefined' + ? column + : renameMapping[column] + ; + if (type !== null || isId) { + // no longer a simple mapping, has need of the type or id properties + renamedColumn = {column: renamedColumn}; + } + if (type !== null) { + // detail the type in the column map if type provided + renamedColumn.type = type; + } + if (isId) { + // set the id property in the column map + renamedColumn.id = true; + // check for any existing id keys that are conflicting + let prevKeyList = keys(pointer[prop]); + for (let k = 0; k < prevKeyList.length; k++) { + if (pointer[prop][prevKeyList[k]].id === true) { + return 'invalid - multiple id - ' + pointer[prop][prevKeyList[k]].column + ' and ' + renamedColumn.column + ' conflict'; + } + } + } + pointer[prop][nav] = j === (navList.length - 1) + ? renamedColumn // is leaf node, store full column string + : null // iteration will replace with object or array + ; + } + pointer = pointer[prop]; + prop = nav; + } + } + } - // return propertyMapping.base; - // }; + return propertyMapping.base; + }; /* Registers a custom type handler */ registerType(name: string, handler: TypeHandler) { From 360955f1e10f1d385597f96eff9f700e75567bcf Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Wed, 6 Mar 2019 18:00:48 -0800 Subject: [PATCH 09/25] refactor tests and remove extraneous files --- lib/test.d.ts | 0 lib/test.js | 81 ------------------ spec/NestHydrationJS/buildMeta.spec.js | 2 +- spec/NestHydrationJS/multipleIds.spec.js | 2 +- spec/NestHydrationJS/nest.spec.js | 2 +- spec/NestHydrationJS/registerType.spec.js | 2 +- ...ructPropToColumnMapFromColumnHints.spec.js | 2 +- spec/NestHydrationJS/typeHandlers.spec.js | 2 +- src/test.ts | 83 ------------------- 9 files changed, 6 insertions(+), 170 deletions(-) delete mode 100644 lib/test.d.ts delete mode 100644 lib/test.js delete mode 100644 src/test.ts diff --git a/lib/test.d.ts b/lib/test.d.ts deleted file mode 100644 index e69de29..0000000 diff --git a/lib/test.js b/lib/test.js deleted file mode 100644 index 03dcc0f..0000000 --- a/lib/test.js +++ /dev/null @@ -1,81 +0,0 @@ -"use strict"; -// var hydration = require('./lib/NestHydrationJS')(); -// let hydrate = hydration(); -// const data = [{ -// 'group_has_service.service_id': 2, -// 'group_has_service.group_id': 5, -// 'zone.id': 5, -// 'zone.name': 'Wings', -// 'service.id': 2, -// 'service.name': 'Deep Clean', -// 'group.id': 5, -// 'group.name': 'Group 5!' -// }, -// { -// 'group_has_service.service_id': 2, -// 'group_has_service.group_id': 5, -// 'zone.id': 4, -// 'zone.name': 'Struts', -// 'service.id': 2, -// 'service.name': 'Deep Clean', -// 'group.id': 5, -// 'group.name': 'Group 5!' -// }, -// { -// 'group_has_service.service_id': 2, -// 'group_has_service.group_id': 5, -// 'zone.id': 3, -// 'zone.name': 'Wheels', -// 'service.id': 2, -// 'service.name': 'Deep Clean', -// 'group.id': 5, -// 'group.name': 'Group 5!' -// }, -// { -// 'group_has_service.service_id': 3, -// 'group_has_service.group_id': 5, -// 'zone.id': 3, -// 'zone.name': 'Wheels', -// 'service.id': 3, -// 'service.name': 'Quick Wash', -// 'group.id': 5, -// 'group.name': 'Group 5!' -// }, -// { -// 'group_has_service.service_id': 3, -// 'group_has_service.group_id': 5, -// 'zone.id': 1, -// 'zone.name': 'Elevators', -// 'service.id': 3, -// 'service.name': 'Quick Wash', -// 'group.id': 5, -// 'group.name': 'Group 5!' -// }, -// { -// 'group_has_service.service_id': 3, -// 'group_has_service.group_id': 5, -// 'zone.id': 6, -// 'zone.name': 'APU', -// 'service.id': 3, -// 'service.name': 'Quick Wash', -// 'group.id': 5, -// 'group.name': 'Group 5!' -// },] -// const description = [{ -// service_id: {column: 'group_has_service.service_id', id: true}, -// group_id: {column: 'group_has_service.group_id'}, -// service: { -// id: 'service.id', -// name: 'service.name', -// zone: [{ -// id: 'zone.id', -// name: 'zone.name' -// }] -// }, -// group: { -// id: 'group.id', -// name: 'group.name' -// } -// }] -// const res = hydrate.nest(data, description); -// console.log(res); diff --git a/spec/NestHydrationJS/buildMeta.spec.js b/spec/NestHydrationJS/buildMeta.spec.js index 69db8e0..6618242 100644 --- a/spec/NestHydrationJS/buildMeta.spec.js +++ b/spec/NestHydrationJS/buildMeta.spec.js @@ -1,6 +1,6 @@ 'use strict'; -var NestHydrationJS = require('../../NestHydrationJS')(); +var NestHydrationJS = require('../../lib/NestHydrationJS')(); describe('NestHydrationJS', function () { describe('buildMeta method', function () { diff --git a/spec/NestHydrationJS/multipleIds.spec.js b/spec/NestHydrationJS/multipleIds.spec.js index 64ef7c1..f1ffa82 100644 --- a/spec/NestHydrationJS/multipleIds.spec.js +++ b/spec/NestHydrationJS/multipleIds.spec.js @@ -1,6 +1,6 @@ 'use strict'; -var NestHydrationJS = require('../../NestHydrationJS')(); +var NestHydrationJS = require('../../lib/NestHydrationJS')(); // fdescribe('NestHydrationJS', function () { // describe('nest method', function () { diff --git a/spec/NestHydrationJS/nest.spec.js b/spec/NestHydrationJS/nest.spec.js index e26e2d2..e41171a 100644 --- a/spec/NestHydrationJS/nest.spec.js +++ b/spec/NestHydrationJS/nest.spec.js @@ -1,6 +1,6 @@ 'use strict'; -var NestHydrationJS = require('../../NestHydrationJS')(); +var NestHydrationJS = require('../../lib/NestHydrationJS')(); describe('NestHydrationJS', function () { describe('nest method', function () { diff --git a/spec/NestHydrationJS/registerType.spec.js b/spec/NestHydrationJS/registerType.spec.js index a312a94..dedd823 100644 --- a/spec/NestHydrationJS/registerType.spec.js +++ b/spec/NestHydrationJS/registerType.spec.js @@ -1,6 +1,6 @@ 'use strict'; -var NestHydrationJS = require('../../NestHydrationJS')(); +var NestHydrationJS = require('../../lib/NestHydrationJS')(); describe('NestHydrationJS', function () { describe('registerType function', function () { diff --git a/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js b/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js index 4f98b5f..9e4d523 100644 --- a/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js +++ b/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js @@ -1,6 +1,6 @@ 'use strict'; -var NestHydrationJS = require('../../NestHydrationJS')(); +var NestHydrationJS = require('../../lib/NestHydrationJS')(); describe('NestHydrationJS', function () { describe('structPropToColumnMapFromColumnHints method', function () { diff --git a/spec/NestHydrationJS/typeHandlers.spec.js b/spec/NestHydrationJS/typeHandlers.spec.js index c94533a..7dcb25b 100644 --- a/spec/NestHydrationJS/typeHandlers.spec.js +++ b/spec/NestHydrationJS/typeHandlers.spec.js @@ -1,6 +1,6 @@ 'use strict'; -var NestHydrationJS = require('../../NestHydrationJS')(); +var NestHydrationJS = require('../../lib/NestHydrationJS')(); describe('NestHydrationJS', function () { describe('typeHandlers', function () { diff --git a/src/test.ts b/src/test.ts deleted file mode 100644 index 453d5fa..0000000 --- a/src/test.ts +++ /dev/null @@ -1,83 +0,0 @@ -// var hydration = require('./lib/NestHydrationJS')(); -// let hydrate = hydration(); - -// const data = [{ -// 'group_has_service.service_id': 2, -// 'group_has_service.group_id': 5, -// 'zone.id': 5, -// 'zone.name': 'Wings', -// 'service.id': 2, -// 'service.name': 'Deep Clean', -// 'group.id': 5, -// 'group.name': 'Group 5!' -// }, -// { -// 'group_has_service.service_id': 2, -// 'group_has_service.group_id': 5, -// 'zone.id': 4, -// 'zone.name': 'Struts', -// 'service.id': 2, -// 'service.name': 'Deep Clean', -// 'group.id': 5, -// 'group.name': 'Group 5!' -// }, -// { -// 'group_has_service.service_id': 2, -// 'group_has_service.group_id': 5, -// 'zone.id': 3, -// 'zone.name': 'Wheels', -// 'service.id': 2, -// 'service.name': 'Deep Clean', -// 'group.id': 5, -// 'group.name': 'Group 5!' -// }, -// { -// 'group_has_service.service_id': 3, -// 'group_has_service.group_id': 5, -// 'zone.id': 3, -// 'zone.name': 'Wheels', -// 'service.id': 3, -// 'service.name': 'Quick Wash', -// 'group.id': 5, -// 'group.name': 'Group 5!' -// }, -// { -// 'group_has_service.service_id': 3, -// 'group_has_service.group_id': 5, -// 'zone.id': 1, -// 'zone.name': 'Elevators', -// 'service.id': 3, -// 'service.name': 'Quick Wash', -// 'group.id': 5, -// 'group.name': 'Group 5!' -// }, -// { -// 'group_has_service.service_id': 3, -// 'group_has_service.group_id': 5, -// 'zone.id': 6, -// 'zone.name': 'APU', -// 'service.id': 3, -// 'service.name': 'Quick Wash', -// 'group.id': 5, -// 'group.name': 'Group 5!' -// },] - -// const description = [{ -// service_id: {column: 'group_has_service.service_id', id: true}, -// group_id: {column: 'group_has_service.group_id'}, -// service: { -// id: 'service.id', -// name: 'service.name', -// zone: [{ -// id: 'zone.id', -// name: 'zone.name' -// }] -// }, -// group: { -// id: 'group.id', -// name: 'group.name' -// } -// }] - -// const res = hydrate.nest(data, description); -// console.log(res); \ No newline at end of file From 71ee7b41bfcccb0380878a4c5424ac3c4ecd1ff5 Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Wed, 6 Mar 2019 23:10:07 -0800 Subject: [PATCH 10/25] finish multi-key implementation and update tests --- .gitignore | 1 + lib/NestHydrationJS.js | 100 ++++++++--------- spec/NestHydrationJS/buildMeta.spec.js | 60 +++++----- ...ructPropToColumnMapFromColumnHints.spec.js | 16 --- src/NestHydrationJS.ts | 103 ++++++++++-------- tsconfig.json | 2 +- 6 files changed, 138 insertions(+), 144 deletions(-) diff --git a/.gitignore b/.gitignore index c2658d7..72237a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules/ +test.js \ No newline at end of file diff --git a/lib/NestHydrationJS.js b/lib/NestHydrationJS.js index 784f462..6414c91 100644 --- a/lib/NestHydrationJS.js +++ b/lib/NestHydrationJS.js @@ -6,6 +6,10 @@ var values = require('lodash.values'); var isPlainObject = require('lodash.isplainobject'); var NestHydrationJS; (function (NestHydrationJS_1) { + function createCompositeKey(values, separator) { + if (separator === void 0) { separator = ', '; } + return values.join(separator); + } var NestHydrationJS = /** @class */ (function () { function NestHydrationJS() { this.typeHandlers = { @@ -73,40 +77,43 @@ var NestHydrationJS; var meta = this.buildMeta(structPropToColumnMap); // BUILD FROM TABLE // defines function that can be called recursively - var _nest = function (row, idColumn) { + var _nest = function (row, idColumns) { // Obj is the actual object that will end up in the final structure var obj; - // Value here is really the id - var value = row[idColumn]; + // Get all of the values for each id + var values = idColumns.map(function (column) { return row[column]; }); // only really concerned with the meta data for this identity column - var objMeta = meta.idMap[idColumn]; - if (value === null) { - if (objMeta.default !== null && typeof objMeta.default !== 'undefined') { - value = objMeta.default; - } - else { - return; + var objMeta = meta.idMap[createCompositeKey(idColumns)]; + // If any of the values are null, we'll check and see if we need to set defaults + values = values.map(function (value, idx) { + if (value === null) { + if (objMeta.defaults[idColumns[idx]] !== null && typeof objMeta.defaults[idColumns[idx]] !== 'undefined') { + return objMeta.defaults[idColumns[idx]]; + } } - } + return value; + }); + if (values.includes(null)) + return; // check if object already exists in cache - if (typeof objMeta.cache[value] !== 'undefined') { + if (typeof objMeta.cache[createCompositeKey(values)] !== 'undefined') { if (objMeta.containingIdUsage === null) return; // We know for certain that containing column is set if // containingIdUsage is not null and can cast it as a string // check and see if this has already been linked to the parent, // and if so we don't need to continue - var containingId = row[objMeta.containingColumn]; - if (typeof objMeta.containingIdUsage[value] !== 'undefined' - && typeof objMeta.containingIdUsage[value][containingId] !== 'undefined') + var containingIds = objMeta.containingColumn.map(function (column) { return row[column]; }); + if (typeof objMeta.containingIdUsage[createCompositeKey(values)] !== 'undefined' + && typeof objMeta.containingIdUsage[createCompositeKey(values)][createCompositeKey(containingIds)] !== 'undefined') return; // not already placed as to-many relation in container - obj = objMeta.cache[value]; + obj = objMeta.cache[createCompositeKey(values)]; } else { // don't have an object defined for this yet, create it and set the cache obj = {}; - objMeta.cache[value] = obj; + objMeta.cache[createCompositeKey(values)] = obj; // copy in properties from table data for (var k = 0; k < objMeta.valueList.length; k++) { var cell = objMeta.valueList[k]; @@ -136,7 +143,7 @@ var NestHydrationJS; // initialize null to-one relations and then recursively build them for (var k = 0; k < objMeta.toOneList.length; k++) { obj[objMeta.toOneList[k].prop] = null; - _nest(row, objMeta.toOneList[k].column); + _nest(row, [objMeta.toOneList[k].column]); } } // link from the parent @@ -155,8 +162,8 @@ var NestHydrationJS; } } else { - var containingId = row[objMeta.containingColumn]; - var container = meta.idMap[objMeta.containingColumn].cache[containingId]; + var containingIds = objMeta.containingColumn.map(function (column) { return row[column]; }); + var container = meta.idMap[createCompositeKey(objMeta.containingColumn)].cache[createCompositeKey(containingIds)]; // If a container exists, it must not be a root, and thus there should // be an ownProp set if (container) { @@ -172,10 +179,10 @@ var NestHydrationJS; // record the containing id so we don't do this again (return in earlier // part of this method) var containingIdUsage = objMeta.containingIdUsage; - if (typeof (containingIdUsage)[value] === 'undefined') { - containingIdUsage[value] = {}; + if (typeof (containingIdUsage)[createCompositeKey(values)] === 'undefined') { + containingIdUsage[createCompositeKey(values)] = {}; } - containingIdUsage[value][containingId] = true; + containingIdUsage[createCompositeKey(values)][createCompositeKey(containingIds)] = true; } }; // struct is populated inside the build function @@ -200,7 +207,7 @@ var NestHydrationJS; var meta; // internally defines recursive function with extra param. This allows cleaner API var _buildMeta = function (structPropToColumnMap, isOneOfMany, containingColumn, ownProp) { - var idProp, subIdColumn; + // var idProp: string | undefined, subIdColumn; var idProps = []; var idColumns = []; var propList = keys(structPropToColumnMap); @@ -210,20 +217,22 @@ var NestHydrationJS; for (var i = 0; i < propList.length; i++) { var prop = propList[i]; if (structPropToColumnMap[prop].id === true) { - idProp = prop; + // idProp = prop; idProps.push(prop); } } - if (idProp === undefined) { - idProp = propList[0]; + if (idProps.length === 0) { + idProps.push(propList[0]); } - // Force we can garuantee that it is a string now, so this will prevent the index error - idProp = idProp; - var idColumn = structPropToColumnMap[idProp].column || structPropToColumnMap[idProp]; - // idColumns = idProps.map(prop => structPropToColumnMap[idProp].column || structPropToColumnMap[idProp]); + // Force we can garuantee that it is a string now, so this will prevent the index error + idColumns = idProps.map(function (prop) { return structPropToColumnMap[prop].column || structPropToColumnMap[prop]; }); if (isOneOfMany) { - meta.primeIdColumnList.push(idColumn); + meta.primeIdColumnList.push(idColumns); } + var defaults = {}; + idProps.forEach(function (prop) { + defaults[prop] = typeof structPropToColumnMap[prop].default === 'undefined' ? null : structPropToColumnMap[prop].default; + }); var objMeta = { valueList: [], toOneList: [], @@ -233,7 +242,7 @@ var NestHydrationJS; isOneOfMany: isOneOfMany === true, cache: {}, containingIdUsage: containingColumn === null ? null : {}, - default: typeof structPropToColumnMap[idProp].default === 'undefined' ? null : structPropToColumnMap[idProp].default + defaults: defaults }; for (var i = 0; i < propList.length; i++) { var prop = propList[i]; @@ -259,28 +268,28 @@ var NestHydrationJS; else if (isArray(structPropToColumnMap[prop])) { // list of objects / to-many relation objMeta.toManyPropList.push(prop); - _buildMeta(structPropToColumnMap[prop][0], true, idColumn, prop); + _buildMeta(structPropToColumnMap[prop][0], true, idColumns, prop); } else if (isPlainObject(structPropToColumnMap[prop])) { // object / to-one relation - var subIdColumn_1 = values(structPropToColumnMap[prop])[0]; - if (typeof subIdColumn_1 === 'undefined') { + var subIdColumn = values(structPropToColumnMap[prop])[0]; + if (typeof subIdColumn === 'undefined') { throw new Error('invalid structPropToColumnMap format - property \'' + prop + '\' can not be an empty object'); } - if (subIdColumn_1.column) { - subIdColumn_1 = subIdColumn_1.column; + if (subIdColumn.column) { + subIdColumn = subIdColumn.column; } objMeta.toOneList.push({ prop: prop, - column: subIdColumn_1 + column: subIdColumn }); - _buildMeta(structPropToColumnMap[prop], false, idColumn, prop); + _buildMeta(structPropToColumnMap[prop], false, idColumns, prop); } else { throw new Error('invalid structPropToColumnMap format - property \'' + prop + '\' must be either a string, a plain object or an array'); } } - meta.idMap[idColumn] = objMeta; + meta.idMap[createCompositeKey(idColumns)] = objMeta; }; // this data structure is populated by the _buildMeta function meta = { @@ -303,7 +312,7 @@ var NestHydrationJS; if (typeof primeIdColumn !== 'string') { primeIdColumn = primeIdColumn.column; } - meta.primeIdColumnList.push(primeIdColumn); + meta.primeIdColumnList.push([primeIdColumn]); // construct the rest _buildMeta(structPropToColumnMap, false, null, null); } @@ -364,13 +373,6 @@ var NestHydrationJS; if (isId) { // set the id property in the column map renamedColumn.id = true; - // check for any existing id keys that are conflicting - var prevKeyList = keys(pointer[prop]); - for (var k = 0; k < prevKeyList.length; k++) { - if (pointer[prop][prevKeyList[k]].id === true) { - return 'invalid - multiple id - ' + pointer[prop][prevKeyList[k]].column + ' and ' + renamedColumn.column + ' conflict'; - } - } } pointer[prop][nav] = j === (navList.length - 1) ? renamedColumn // is leaf node, store full column string diff --git a/spec/NestHydrationJS/buildMeta.spec.js b/spec/NestHydrationJS/buildMeta.spec.js index 6618242..9b01105 100644 --- a/spec/NestHydrationJS/buildMeta.spec.js +++ b/spec/NestHydrationJS/buildMeta.spec.js @@ -15,7 +15,7 @@ describe('NestHydrationJS', function () { it('should match expected structure', function () { var expected = { - primeIdColumnList: ['aColumnName'], + primeIdColumnList: [['aColumnName']], idMap: { aColumnName: { valueList: [ @@ -28,7 +28,7 @@ describe('NestHydrationJS', function () { isOneOfMany: false, cache: {}, containingIdUsage: null, - default: null + defaults: {a: null} } } }; @@ -47,7 +47,7 @@ describe('NestHydrationJS', function () { it('should match expected structure', function () { var expected = { - primeIdColumnList: ['a'], + primeIdColumnList: [['a']], idMap: { a: { valueList: [ @@ -60,7 +60,7 @@ describe('NestHydrationJS', function () { isOneOfMany: false, cache: {}, containingIdUsage: null, - default: null + defaults: {a: null} } } }; @@ -79,7 +79,7 @@ describe('NestHydrationJS', function () { it('should match expected structure', function () { var expected = { - primeIdColumnList: ['a'], + primeIdColumnList: [['a']], idMap: { a: { valueList: [ @@ -92,7 +92,7 @@ describe('NestHydrationJS', function () { isOneOfMany: false, cache: {}, containingIdUsage: null, - default: null + defaults: {a: null} } } }; @@ -111,7 +111,7 @@ describe('NestHydrationJS', function () { it('should match expected structure', function () { var expected = { - primeIdColumnList: ['a'], + primeIdColumnList: [['a']], idMap: { a: { valueList: [ @@ -124,7 +124,7 @@ describe('NestHydrationJS', function () { isOneOfMany: false, cache: {}, containingIdUsage: null, - default: 'a_default' + defaults: {a: 'a_default'} } } }; @@ -144,7 +144,7 @@ describe('NestHydrationJS', function () { it('should match expected structure', function () { var expected = { - primeIdColumnList: ['a'], + primeIdColumnList: [['a']], idMap: { a: { valueList: [ @@ -158,7 +158,7 @@ describe('NestHydrationJS', function () { isOneOfMany: false, cache: {}, containingIdUsage: null, - default: null + defaults: {a: null} } } }; @@ -178,7 +178,7 @@ describe('NestHydrationJS', function () { it('should match expected structure', function () { var expected = { - primeIdColumnList: ['a'], + primeIdColumnList: [['a']], idMap: { a: { valueList: [ @@ -192,7 +192,7 @@ describe('NestHydrationJS', function () { isOneOfMany: false, cache: {}, containingIdUsage: null, - default: 'a_default' + defaults: {a: 'a_default'} } } }; @@ -215,7 +215,7 @@ describe('NestHydrationJS', function () { it('should match expected structure', function () { var expected = { - primeIdColumnList: ['id'], + primeIdColumnList: [['id']], idMap: { c: { valueList: [ @@ -223,12 +223,12 @@ describe('NestHydrationJS', function () { ], toOneList: [], toManyPropList: [], - containingColumn: 'id', + containingColumn: ['id'], ownProp: 'a', isOneOfMany: false, cache: {}, containingIdUsage: {}, - default: 'c_default' + defaults: {c: 'c_default'} }, id: { valueList: [ @@ -242,7 +242,7 @@ describe('NestHydrationJS', function () { isOneOfMany: false, cache: {}, containingIdUsage: null, - default: null + defaults: { id: null } } } }; @@ -262,7 +262,7 @@ describe('NestHydrationJS', function () { it('should match expected structure', function () { var expected = { - primeIdColumnList: ['_a'], + primeIdColumnList: [['_a']], idMap: { _a: { valueList: [ @@ -276,7 +276,7 @@ describe('NestHydrationJS', function () { isOneOfMany: true, cache: {}, containingIdUsage: null, - default: null + defaults: { a: null } } } }; @@ -296,7 +296,7 @@ describe('NestHydrationJS', function () { it('should match expected structure', function () { var expected = { - primeIdColumnList: ['_b'], + primeIdColumnList: [['_b']], idMap: { _b: { valueList: [ @@ -310,7 +310,7 @@ describe('NestHydrationJS', function () { isOneOfMany: true, cache: {}, containingIdUsage: null, - default: null + defaults: {b: null} } } }; @@ -330,7 +330,7 @@ describe('NestHydrationJS', function () { it('should match expected structure', function () { var expected = { - primeIdColumnList: ['_a'], + primeIdColumnList: [['_a']], idMap: { _a: { valueList: [ @@ -344,7 +344,7 @@ describe('NestHydrationJS', function () { isOneOfMany: true, cache: {}, containingIdUsage: null, - default: null + defaults: {a: null} } } }; @@ -364,7 +364,7 @@ describe('NestHydrationJS', function () { it('should match expected structure', function () { var expected = { - primeIdColumnList: ['_a'], + primeIdColumnList: [['_a']], idMap: { _a: { valueList: [ @@ -378,7 +378,7 @@ describe('NestHydrationJS', function () { isOneOfMany: true, cache: {}, containingIdUsage: null, - default: 'a_default' + defaults: {a: 'a_default'} } } }; @@ -405,7 +405,7 @@ describe('NestHydrationJS', function () { it('should match expected structure', function () { var expected = { - primeIdColumnList: ['_a', '_e__f'], + primeIdColumnList: [['_a'], ['_e__f']], idMap: { '_a': { valueList: [ @@ -423,7 +423,7 @@ describe('NestHydrationJS', function () { isOneOfMany: true, cache: {}, containingIdUsage: null, - default: null + defaults: { a: null } }, '_c_d': { valueList: [ @@ -431,12 +431,12 @@ describe('NestHydrationJS', function () { ], toOneList: [], toManyPropList: [], - containingColumn: '_a', + containingColumn: ['_a'], ownProp: 'c', isOneOfMany: false, cache: {}, containingIdUsage: {}, - default: null + defaults: { d: null } }, '_e__f': { valueList: [ @@ -445,12 +445,12 @@ describe('NestHydrationJS', function () { ], toOneList: [], toManyPropList: [], - containingColumn: '_a', + containingColumn: ['_a'], ownProp: 'e', isOneOfMany: true, cache: {}, containingIdUsage: {}, - default: null + defaults: { f: null } } } }; diff --git a/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js b/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js index 9e4d523..fb1e9b1 100644 --- a/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js +++ b/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js @@ -84,22 +84,6 @@ describe('NestHydrationJS', function () { }); }); - describe('passed single direct property as columnList, multiple id columns', function () { - var result; - beforeEach(function () { - var columnList = [ - 'a___ID', - 'b___ID' - ]; - result = NestHydrationJS.structPropToColumnMapFromColumnHints(columnList); - }); - - it('should match expected structure', function () { - var expected = 'invalid - multiple id - a___ID and b___ID conflict'; - expect(result).toEqual(expected); - }); - }); - describe('passed single direct property as columnList, id column and typed', function () { var result; beforeEach(function () { diff --git a/src/NestHydrationJS.ts b/src/NestHydrationJS.ts index 225fc2d..b26c58a 100644 --- a/src/NestHydrationJS.ts +++ b/src/NestHydrationJS.ts @@ -1,5 +1,7 @@ 'use strict'; +import { create } from "domain"; + const isArray = require('lodash.isarray'); const isFunction = require('lodash.isfunction'); const keys = require('lodash.keys'); @@ -30,16 +32,16 @@ module NestHydrationJS { valueList: Array, toOneList: Array, toManyPropList: Array, - containingColumn: string | null, + containingColumn: Array | null, ownProp: string | null, isOneOfMany: boolean, cache: Dictionary, containingIdUsage: Dictionary> | null, - default?: any + defaults: Dictionary } interface MetaData { - primeIdColumnList: Array, + primeIdColumnList: Array>, idMap: { [index: string]: MetaColumnData } } @@ -59,6 +61,10 @@ module NestHydrationJS { [index: number]: any } + function createCompositeKey(values: Array, separator = ', '): string { + return values.join(separator); + } + export class NestHydrationJS { private typeHandlers = { @@ -133,30 +139,35 @@ module NestHydrationJS { // COMPLETE VALIDATING PARAMS AND BASIC INITIALIZATION let meta = this.buildMeta(structPropToColumnMap); + // BUILD FROM TABLE // defines function that can be called recursively - let _nest = (row: Dictionary, idColumn: string) => { + let _nest = (row: Dictionary, idColumns: string[]) => { // Obj is the actual object that will end up in the final structure let obj: Data; - // Value here is really the id - let value: any = row[idColumn]; + // Get all of the values for each id + let values: Array = idColumns.map(column => row[column]); // only really concerned with the meta data for this identity column - let objMeta = meta.idMap[idColumn]; + let objMeta = meta.idMap[createCompositeKey(idColumns)]; - if (value === null) { - if (objMeta.default !== null && typeof objMeta.default !== 'undefined') { - value = objMeta.default; - } else { - return; + // If any of the values are null, we'll check and see if we need to set defaults + values = values.map((value, idx) => { + if (value === null) { + if (objMeta.defaults[idColumns[idx]] !== null && typeof objMeta.defaults[idColumns[idx]] !== 'undefined') { + return objMeta.defaults[idColumns[idx]]; + } } - } + return value; + }) + + if (values.includes(null)) return; // check if object already exists in cache - if (typeof objMeta.cache[value] !== 'undefined') { + if (typeof objMeta.cache[createCompositeKey(values)] !== 'undefined') { if (objMeta.containingIdUsage === null) return; @@ -165,17 +176,17 @@ module NestHydrationJS { // check and see if this has already been linked to the parent, // and if so we don't need to continue - let containingId = row[objMeta.containingColumn]; - if (typeof objMeta.containingIdUsage[value] !== 'undefined' - && typeof objMeta.containingIdUsage[value][containingId] !== 'undefined' + let containingIds = (>objMeta.containingColumn).map(column => row[column]); + if (typeof objMeta.containingIdUsage[createCompositeKey(values)] !== 'undefined' + && typeof objMeta.containingIdUsage[createCompositeKey(values)][createCompositeKey(containingIds)] !== 'undefined' ) return; // not already placed as to-many relation in container - obj = objMeta.cache[value]; + obj = objMeta.cache[createCompositeKey(values)]; } else { // don't have an object defined for this yet, create it and set the cache obj = {}; - objMeta.cache[value] = obj; + objMeta.cache[createCompositeKey(values)] = obj; // copy in properties from table data for (let k = 0; k < objMeta.valueList.length; k++) { @@ -209,7 +220,7 @@ module NestHydrationJS { // initialize null to-one relations and then recursively build them for (let k = 0; k < objMeta.toOneList.length; k++) { obj[objMeta.toOneList[k].prop] = null; - _nest(row, objMeta.toOneList[k].column); + _nest(row, [objMeta.toOneList[k].column]); } } @@ -227,8 +238,8 @@ module NestHydrationJS { this.struct = obj; } } else { - let containingId = row[objMeta.containingColumn]; - let container = meta.idMap[objMeta.containingColumn].cache[containingId]; + let containingIds = objMeta.containingColumn.map(column => row[column]); + let container = meta.idMap[createCompositeKey(objMeta.containingColumn)].cache[createCompositeKey(containingIds)]; // If a container exists, it must not be a root, and thus there should // be an ownProp set @@ -245,10 +256,10 @@ module NestHydrationJS { // record the containing id so we don't do this again (return in earlier // part of this method) const containingIdUsage = >>objMeta.containingIdUsage - if (typeof (containingIdUsage)[value] === 'undefined') { - containingIdUsage[value] = {}; + if (typeof (containingIdUsage)[createCompositeKey(values)] === 'undefined') { + containingIdUsage[createCompositeKey(values)] = {}; } - containingIdUsage[value][containingId] = true; + containingIdUsage[createCompositeKey(values)][createCompositeKey(containingIds)] = true; } }; @@ -282,10 +293,10 @@ module NestHydrationJS { let _buildMeta = function( structPropToColumnMap: Definition, isOneOfMany: boolean, - containingColumn: string | null, + containingColumn: Array | null, ownProp: string | null) { - var idProp: string | undefined, subIdColumn; + // var idProp: string | undefined, subIdColumn; let idProps = []; let idColumns = []; @@ -298,24 +309,27 @@ module NestHydrationJS { for (let i = 0; i < propList.length; i++) { let prop = propList[i]; if ((structPropToColumnMap[prop]).id === true) { - idProp = prop; + // idProp = prop; idProps.push(prop); } } - if (idProp === undefined) { - idProp = propList[0]; + if (idProps.length === 0) { + idProps.push(propList[0]); } - // Force we can garuantee that it is a string now, so this will prevent the index error - idProp = idProp as string - - let idColumn = (structPropToColumnMap[idProp]).column || structPropToColumnMap[idProp] as string; - // idColumns = idProps.map(prop => structPropToColumnMap[idProp].column || structPropToColumnMap[idProp]); + // Force we can garuantee that it is a string now, so this will prevent the index error + idColumns = idProps.map(prop => (structPropToColumnMap[prop]).column || structPropToColumnMap[prop]) as Array; if (isOneOfMany) { - meta.primeIdColumnList.push(idColumn); + meta.primeIdColumnList.push(idColumns); } + + let defaults: Dictionary = {} + + idProps.forEach(prop => { + defaults[prop] = typeof (structPropToColumnMap[prop]).default === 'undefined' ? null : (structPropToColumnMap[prop]).default + }) let objMeta: MetaColumnData = { valueList: [], @@ -326,7 +340,7 @@ module NestHydrationJS { isOneOfMany: isOneOfMany === true, cache: {}, containingIdUsage: containingColumn === null ? null : {}, - default: typeof (structPropToColumnMap[idProp]).default === 'undefined' ? null : (structPropToColumnMap[idProp]).default + defaults: defaults }; for (let i = 0; i < propList.length; i++) { @@ -352,7 +366,7 @@ module NestHydrationJS { // list of objects / to-many relation objMeta.toManyPropList.push(prop); - _buildMeta((>structPropToColumnMap[prop])[0], true, idColumn, prop); + _buildMeta((>structPropToColumnMap[prop])[0], true, idColumns, prop); } else if (isPlainObject(structPropToColumnMap[prop])) { // object / to-one relation @@ -369,13 +383,13 @@ module NestHydrationJS { prop: prop, column: subIdColumn }); - _buildMeta(structPropToColumnMap[prop], false, idColumn, prop); + _buildMeta(structPropToColumnMap[prop], false, idColumns, prop); } else { throw new Error('invalid structPropToColumnMap format - property \'' + prop + '\' must be either a string, a plain object or an array'); } } - meta.idMap[idColumn] = objMeta; + meta.idMap[createCompositeKey(idColumns)] = objMeta; }; // this data structure is populated by the _buildMeta function @@ -401,7 +415,7 @@ module NestHydrationJS { primeIdColumn = primeIdColumn.column; } - meta.primeIdColumnList.push(primeIdColumn); + meta.primeIdColumnList.push([primeIdColumn]); // construct the rest _buildMeta(structPropToColumnMap, false, null, null); @@ -471,13 +485,6 @@ module NestHydrationJS { if (isId) { // set the id property in the column map renamedColumn.id = true; - // check for any existing id keys that are conflicting - let prevKeyList = keys(pointer[prop]); - for (let k = 0; k < prevKeyList.length; k++) { - if (pointer[prop][prevKeyList[k]].id === true) { - return 'invalid - multiple id - ' + pointer[prop][prevKeyList[k]].column + ' and ' + renamedColumn.column + ' conflict'; - } - } } pointer[prop][nav] = j === (navList.length - 1) ? renamedColumn // is leaf node, store full column string diff --git a/tsconfig.json b/tsconfig.json index 04d97b8..764f7f7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,6 @@ "outDir": "./lib", "strict": true }, - "include": ["src/**/*"], + "include": ["src/**/*", "test.ts"], "exclude": ["node_modules/", "spec"] } From 68db17a81950b385c12b14ba3f3ad096f88d7d95 Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Thu, 7 Mar 2019 14:40:19 -0800 Subject: [PATCH 11/25] update tests and fix bug with prime base object --- lib/NestHydrationJS.d.ts | 2 +- lib/NestHydrationJS.js | 36 ++++++--- spec/NestHydrationJS/buildMeta.spec.js | 68 +++++++++++++++++ spec/NestHydrationJS/multipleIds.spec.js | 53 -------------- spec/NestHydrationJS/nest.spec.js | 73 +++++++++++++++++++ ...ructPropToColumnMapFromColumnHints.spec.js | 19 +++++ src/NestHydrationJS.ts | 39 +++++++--- 7 files changed, 216 insertions(+), 74 deletions(-) delete mode 100644 spec/NestHydrationJS/multipleIds.spec.js diff --git a/lib/NestHydrationJS.d.ts b/lib/NestHydrationJS.d.ts index 48b7a3e..a40db65 100644 --- a/lib/NestHydrationJS.d.ts +++ b/lib/NestHydrationJS.d.ts @@ -17,7 +17,7 @@ declare module NestHydrationJS { class NestHydrationJS { private typeHandlers; private struct; - nest(data: any, structPropToColumnMap: Definition | Definition[] | null | boolean): any; + nest(data: any, structPropToColumnMap: Definition | Definition[] | null | boolean, verbose?: boolean): any; private buildMeta; structPropToColumnMapFromColumnHints(columnList: Array, renameMapping?: Dictionary): any; registerType(name: string, handler: TypeHandler): void; diff --git a/lib/NestHydrationJS.js b/lib/NestHydrationJS.js index 6414c91..4fb0c11 100644 --- a/lib/NestHydrationJS.js +++ b/lib/NestHydrationJS.js @@ -28,8 +28,9 @@ var NestHydrationJS; * the data has column names that follow a particular convention then a * nested structures can also be created. */ - NestHydrationJS.prototype.nest = function (data, structPropToColumnMap) { + NestHydrationJS.prototype.nest = function (data, structPropToColumnMap, verbose) { var _this = this; + if (verbose === void 0) { verbose = false; } var table; // VALIDATE PARAMS AND BASIC INITIALIZATION // Determines that on no results, and empty list is used instead of null. // NOTE: fact check this @@ -74,12 +75,14 @@ var NestHydrationJS; return isArray(structPropToColumnMap) ? [] : null; } // COMPLETE VALIDATING PARAMS AND BASIC INITIALIZATION - var meta = this.buildMeta(structPropToColumnMap); + var meta = this.buildMeta(structPropToColumnMap, verbose); // BUILD FROM TABLE // defines function that can be called recursively var _nest = function (row, idColumns) { // Obj is the actual object that will end up in the final structure var obj; + if (verbose) + console.log(meta); // Get all of the values for each id var values = idColumns.map(function (column) { return row[column]; }); // only really concerned with the meta data for this identity column @@ -187,6 +190,8 @@ var NestHydrationJS; }; // struct is populated inside the build function this.struct = null; + if (verbose) + console.log(meta); for (var i = 0; i < table.length; i++) { // go through each row of the table var row = table[i]; @@ -203,7 +208,8 @@ var NestHydrationJS; /* Create a data structure that contains lookups and cache spaces for quick * reference and action for the workings of the nest method. */ - NestHydrationJS.prototype.buildMeta = function (structPropToColumnMap) { + NestHydrationJS.prototype.buildMeta = function (structPropToColumnMap, verbose) { + if (verbose === void 0) { verbose = false; } var meta; // internally defines recursive function with extra param. This allows cleaner API var _buildMeta = function (structPropToColumnMap, isOneOfMany, containingColumn, ownProp) { @@ -224,7 +230,6 @@ var NestHydrationJS; if (idProps.length === 0) { idProps.push(propList[0]); } - // Force we can garuantee that it is a string now, so this will prevent the index error idColumns = idProps.map(function (prop) { return structPropToColumnMap[prop].column || structPropToColumnMap[prop]; }); if (isOneOfMany) { meta.primeIdColumnList.push(idColumns); @@ -305,14 +310,27 @@ var NestHydrationJS; } else if (isPlainObject(structPropToColumnMap)) { // register first column as prime id column - var primeIdColumn = values(structPropToColumnMap)[0]; - if (typeof primeIdColumn === 'undefined') { + var columns = values(structPropToColumnMap); + if (columns.length === 0) { throw new Error('invalid structPropToColumnMap format - the base object can not be an empty object'); } - if (typeof primeIdColumn !== 'string') { - primeIdColumn = primeIdColumn.column; + // First determine if there are any keys set on the columns + var keys_1 = columns.reduce(function (accumulator, column) { + if (column.id === true) { + accumulator.push(column.column); + } + return accumulator; + }, []); + // If there were no keys set, then take the first column as the id + if (keys_1.length === 0) { + if (typeof columns[0] === 'string') { + keys_1.push(columns[0]); + } + else if (typeof columns[0].column === 'string') { + keys_1.push(columns[0].column); + } } - meta.primeIdColumnList.push([primeIdColumn]); + meta.primeIdColumnList.push(keys_1); // construct the rest _buildMeta(structPropToColumnMap, false, null, null); } diff --git a/spec/NestHydrationJS/buildMeta.spec.js b/spec/NestHydrationJS/buildMeta.spec.js index 9b01105..a85a245 100644 --- a/spec/NestHydrationJS/buildMeta.spec.js +++ b/spec/NestHydrationJS/buildMeta.spec.js @@ -386,6 +386,74 @@ describe('NestHydrationJS', function () { }); }); + describe('multiple mapping array with composite id', function () { + var result; + beforeEach(function () { + var mapping = [{ + a: {column: '_a', id: true}, + b: {column: '_b', id: true} + }]; + result = NestHydrationJS.buildMeta(mapping); + }); + + it('should match expected structure', function () { + var expected = { + primeIdColumnList: [['_a', '_b']], + idMap: { + '_a, _b': { + valueList: [ + {prop: 'a', column: '_a', type: undefined, default: undefined}, + {prop: 'b', column: '_b', type: undefined, default: undefined} + ], + toOneList: [], + toManyPropList: [], + containingColumn: null, + ownProp: null, + isOneOfMany: true, + cache: {}, + containingIdUsage: null, + defaults: {a: null, b: null} + } + } + }; + expect(result).toEqual(expected); + }); + }); + + describe('multiple mapping array with composite id and defaults', function () { + var result; + beforeEach(function () { + var mapping = [{ + a: {column: '_a', id: true, default: 'a_default'}, + b: {column: '_b', id: true, default: 'b_default'} + }]; + result = NestHydrationJS.buildMeta(mapping); + }); + + it('should match expected structure', function () { + var expected = { + primeIdColumnList: [['_a', '_b']], + idMap: { + '_a, _b': { + valueList: [ + {prop: 'a', column: '_a', type: undefined, default: 'a_default'}, + {prop: 'b', column: '_b', type: undefined, default: 'b_default'} + ], + toOneList: [], + toManyPropList: [], + containingColumn: null, + ownProp: null, + isOneOfMany: true, + cache: {}, + containingIdUsage: null, + defaults: {a: 'a_default', b: 'b_default'} + } + } + }; + expect(result).toEqual(expected); + }); + }); + describe('multiple mapping complex', function () { var result; beforeEach(function () { diff --git a/spec/NestHydrationJS/multipleIds.spec.js b/spec/NestHydrationJS/multipleIds.spec.js deleted file mode 100644 index f1ffa82..0000000 --- a/spec/NestHydrationJS/multipleIds.spec.js +++ /dev/null @@ -1,53 +0,0 @@ -'use strict'; - -var NestHydrationJS = require('../../lib/NestHydrationJS')(); - -// fdescribe('NestHydrationJS', function () { -// describe('nest method', function () { -// describe('Documentation Example 1', function () { -// var result; -// beforeEach(function () { -// var table = [ -// {id: '1', title: 'Tabular to Objects', teacher_id: '1', teacher_name: 'David', lesson_id: '1', lesson_title: 'Definitions' }, -// {id: '1', title: 'Tabular to Objects', teacher_id: '1', teacher_name: 'David', lesson_id: '2', lesson_title: 'Table Data' }, -// {id: '1', title: 'Tabular to Objects', teacher_id: '1', teacher_name: 'David', lesson_id: '3', lesson_title: 'Objects' }, -// {id: '2', title: 'Column Names Define Structure', teacher_id: '2', teacher_name: 'Chris', lesson_id: '4', lesson_title: 'Column Names' }, -// {id: '2', title: 'Column Names Define Structure', teacher_id: '2', teacher_name: 'Chris', lesson_id: '2', lesson_title: 'Table Data' }, -// {id: '2', title: 'Column Names Define Structure', teacher_id: '2', teacher_name: 'Chris', lesson_id: '3', lesson_title: 'Objects' }, -// {id: '3', title: 'Object On Bottom', teacher_id: '1', teacher_name: 'David', lesson_id: '5', lesson_title: 'Non Array Input'} -// ]; -// var definition = [{ -// id: 'id', -// title: 'title', -// teacher: { -// id: 'teacher_id', -// name: 'teacher_name' -// }, -// lesson: [{ -// id: 'lesson_id', -// title: 'lesson_title' -// }] -// }]; -// result = NestHydrationJS.nest(table, definition); -// }); -// it('should match expected structure', function () { -// var expected = [ -// {id: '1', title: 'Tabular to Objects', teacher: {id: '1', name: 'David'}, lesson: [ -// {id: '1', title: 'Definitions'}, -// {id: '2', title: 'Table Data'}, -// {id: '3', title: 'Objects'} -// ]}, -// {id: '2', title: 'Column Names Define Structure', teacher: {id: '2', name: 'Chris'}, lesson: [ -// {id: '4', title: 'Column Names'}, -// {id: '2', title: 'Table Data'}, -// {id: '3', title: 'Objects'} -// ]}, -// {id: '3', title: 'Object On Bottom', teacher: {id: '1', name: 'David'}, lesson: [ -// {id: '5', title: 'Non Array Input'} -// ]} -// ]; -// expect(result).toEqual({}); -// }); -// }); -// }); -// }); diff --git a/spec/NestHydrationJS/nest.spec.js b/spec/NestHydrationJS/nest.spec.js index e41171a..4f614b2 100644 --- a/spec/NestHydrationJS/nest.spec.js +++ b/spec/NestHydrationJS/nest.spec.js @@ -381,6 +381,79 @@ describe('NestHydrationJS', function () { }); }); + describe('multiple mapping array with composite id', function () { + var result; + beforeEach(function () { + var mapping = [{ + a: {column: 'a', id: true}, + b: {column: 'b', id: true} + }]; + var data = [ + {a: 'value a1', b: 'value b1'}, + {a: 'value a1', b: 'value b2'}, + {a: 'value a2', b: 'value b1'}, + {a: 'value a2', b: 'value b2'} + ]; + result = NestHydrationJS.nest(data, mapping); + }); + + it('should match expected structure', function () { + var expected = [ + {a: 'value a1', b: 'value b1'}, + {a: 'value a1', b: 'value b2'}, + {a: 'value a2', b: 'value b1'}, + {a: 'value a2', b: 'value b2'} + ]; + expect(result).toEqual(expected); + }); + }); + + describe('multiple mapping array with composite id and multiple mapping', function () { + var result; + beforeEach(function () { + var mapping = [{ + a: {column: 'a', id: true}, + b: {column: 'b', id: true}, + c: [{ + id: 'c_id' + }] + }]; + var data = [ + {a: 'a1', b: 'b1', c_id: 'c1'}, + {a: 'a1', b: 'b1', c_id: 'c2'}, + {a: 'a2', b: 'b1', c_id: 'c1'}, + {a: 'a2', b: 'b1', c_id: 'c2'} + ]; + result = NestHydrationJS.nest(data, mapping); + }); + + it('should match expected structure', function () { + var expected = [ + {a: 'a1', b: 'b1', c: [ { id: 'c1'} , { id: 'c2' } ]}, + {a: 'a2', b: 'b1', c: [ { id: 'c1'} , { id: 'c2' } ]}, + ]; + expect(result).toEqual(expected); + }); + }); + + describe('multiple mapping array with composite id and multiple mapping', function () { + var result; + beforeEach(function () { + var mapping = { + a: {column: 'a', id: true}, + b: {column: 'b', id: true}, + }; + var data = {a: 'a1', b: 'b1'}; + result = NestHydrationJS.nest(data, mapping, true); + }); + + it('should match expected structure', function () { + var expected = {a: 'a1', b: 'b1'}; + console.log("result: ", result) + expect(result).toEqual(expected); + }); + }); + describe('multiple mapping array, hinted mapping', function () { var result; beforeEach(function () { diff --git a/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js b/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js index fb1e9b1..2c1651d 100644 --- a/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js +++ b/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js @@ -83,6 +83,25 @@ describe('NestHydrationJS', function () { expect(result).toEqual(expected); }); }); + + describe('passed two properties as columnList both as id columns', function () { + var result; + beforeEach(function () { + var columnList = [ + 'a___ID', + 'b___ID' + ]; + result = NestHydrationJS.structPropToColumnMapFromColumnHints(columnList); + }); + + it('should match expected structure', function () { + var expected = { + a: {column: 'a___ID', id: true}, + b: {column: 'b___ID', id: true} + }; + expect(result).toEqual(expected); + }); + }); describe('passed single direct property as columnList, id column and typed', function () { var result; diff --git a/src/NestHydrationJS.ts b/src/NestHydrationJS.ts index b26c58a..c73ccfe 100644 --- a/src/NestHydrationJS.ts +++ b/src/NestHydrationJS.ts @@ -84,7 +84,7 @@ module NestHydrationJS { * the data has column names that follow a particular convention then a * nested structures can also be created. */ - nest(data: any, structPropToColumnMap: Definition | Definition[] | null | boolean): any { + nest(data: any, structPropToColumnMap: Definition | Definition[] | null | boolean, verbose = false): any { let table; @@ -138,7 +138,7 @@ module NestHydrationJS { // COMPLETE VALIDATING PARAMS AND BASIC INITIALIZATION - let meta = this.buildMeta(structPropToColumnMap); + let meta = this.buildMeta(structPropToColumnMap, verbose); // BUILD FROM TABLE @@ -147,6 +147,8 @@ module NestHydrationJS { // Obj is the actual object that will end up in the final structure let obj: Data; + + if (verbose) console.log(meta); // Get all of the values for each id let values: Array = idColumns.map(column => row[column]); @@ -265,6 +267,8 @@ module NestHydrationJS { // struct is populated inside the build function this.struct = null; + + if (verbose) console.log(meta); for (let i = 0; i < table.length; i++) { // go through each row of the table @@ -274,7 +278,6 @@ module NestHydrationJS { // for each prime id column (corresponding to a to-many relation or // the top level) attempted to build an object let primeIdColumn = meta.primeIdColumnList[j]; - _nest(row, primeIdColumn); } } @@ -285,7 +288,7 @@ module NestHydrationJS { /* Create a data structure that contains lookups and cache spaces for quick * reference and action for the workings of the nest method. */ - private buildMeta(structPropToColumnMap: Definition | Definition[]): MetaData { + private buildMeta(structPropToColumnMap: Definition | Definition[], verbose = false): MetaData { var meta: MetaData; @@ -318,7 +321,6 @@ module NestHydrationJS { idProps.push(propList[0]); } - // Force we can garuantee that it is a string now, so this will prevent the index error idColumns = idProps.map(prop => (structPropToColumnMap[prop]).column || structPropToColumnMap[prop]) as Array; if (isOneOfMany) { @@ -405,17 +407,32 @@ module NestHydrationJS { // call with first object, but inform _buildMeta it is an array _buildMeta((>structPropToColumnMap)[0], true, null, null); } else if (isPlainObject(structPropToColumnMap)) { + // register first column as prime id column - let primeIdColumn = values(structPropToColumnMap)[0]; - if (typeof primeIdColumn === 'undefined') { + let columns = values(structPropToColumnMap) as any[]; + + if (columns.length === 0) { throw new Error('invalid structPropToColumnMap format - the base object can not be an empty object'); } + + // First determine if there are any keys set on the columns + let keys = columns.reduce((accumulator: string[], column: any) => { + if (column.id === true) { + accumulator.push(column.column) + } + return accumulator; + }, []); - if (typeof primeIdColumn !== 'string') { - primeIdColumn = primeIdColumn.column; + // If there were no keys set, then take the first column as the id + if (keys.length === 0) { + if (typeof columns[0] === 'string') { + keys.push(columns[0]) + } else if(typeof columns[0].column === 'string') { + keys.push(columns[0].column) + } } - - meta.primeIdColumnList.push([primeIdColumn]); + + meta.primeIdColumnList.push(keys); // construct the rest _buildMeta(structPropToColumnMap, false, null, null); From c9b290da58c46b0b0d08e4ac2955328d55adee26 Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Thu, 7 Mar 2019 15:22:08 -0800 Subject: [PATCH 12/25] add array support --- lib/NestHydrationJS.d.ts | 2 + lib/NestHydrationJS.js | 87 +++++++++++++++++++++++++----------- src/NestHydrationJS.ts | 96 ++++++++++++++++++++++++++++------------ 3 files changed, 131 insertions(+), 54 deletions(-) diff --git a/lib/NestHydrationJS.d.ts b/lib/NestHydrationJS.d.ts index a40db65..1542b23 100644 --- a/lib/NestHydrationJS.d.ts +++ b/lib/NestHydrationJS.d.ts @@ -10,6 +10,7 @@ declare module NestHydrationJS { id?: boolean; default?: any; type?: string; + array?: boolean; } interface Definition { [index: string]: DefinitionColumn | string | Definition | Definition[]; @@ -17,6 +18,7 @@ declare module NestHydrationJS { class NestHydrationJS { private typeHandlers; private struct; + private computeActualCellValue; nest(data: any, structPropToColumnMap: Definition | Definition[] | null | boolean, verbose?: boolean): any; private buildMeta; structPropToColumnMapFromColumnHints(columnList: Array, renameMapping?: Dictionary): any; diff --git a/lib/NestHydrationJS.js b/lib/NestHydrationJS.js index 4fb0c11..b565475 100644 --- a/lib/NestHydrationJS.js +++ b/lib/NestHydrationJS.js @@ -22,6 +22,25 @@ var NestHydrationJS; }; this.struct = null; } + NestHydrationJS.prototype.computeActualCellValue = function (props, initialValue) { + var cellValue = initialValue; + if (cellValue !== null) { + var valueTypeFunction = void 0; + if (isFunction(props.type)) { + valueTypeFunction = props.type; + } + else if (typeof props.type === 'string') { + valueTypeFunction = this.typeHandlers[props.type]; + } + if (valueTypeFunction) { + cellValue = valueTypeFunction(cellValue); + } + } + else if (typeof props.default !== 'undefined') { + cellValue = props.default; + } + return cellValue; + }; /* Creates a data structure containing nested objects and/or arrays from * tabular data based on a structure definition provided by * structPropToColumnMap. If structPropToColumnMap is not provided but @@ -81,8 +100,6 @@ var NestHydrationJS; var _nest = function (row, idColumns) { // Obj is the actual object that will end up in the final structure var obj; - if (verbose) - console.log(meta); // Get all of the values for each id var values = idColumns.map(function (column) { return row[column]; }); // only really concerned with the meta data for this identity column @@ -100,43 +117,55 @@ var NestHydrationJS; return; // check if object already exists in cache if (typeof objMeta.cache[createCompositeKey(values)] !== 'undefined') { + // not already placed as to-many relation in container + obj = objMeta.cache[createCompositeKey(values)]; + // Add array values + if (objMeta.arraysList.length > 0) { + for (var _i = 0, _a = objMeta.arraysList; _i < _a.length; _i++) { + var prop = _a[_i]; + var cellValue = _this.computeActualCellValue(prop, row[prop.column]); + if (isArray(obj[prop.prop])) { + obj[prop.prop].push(cellValue); + } + } + } if (objMeta.containingIdUsage === null) return; // We know for certain that containing column is set if // containingIdUsage is not null and can cast it as a string // check and see if this has already been linked to the parent, + // doesn't contain any array lists, // and if so we don't need to continue var containingIds = objMeta.containingColumn.map(function (column) { return row[column]; }); if (typeof objMeta.containingIdUsage[createCompositeKey(values)] !== 'undefined' - && typeof objMeta.containingIdUsage[createCompositeKey(values)][createCompositeKey(containingIds)] !== 'undefined') + && typeof objMeta.containingIdUsage[createCompositeKey(values)][createCompositeKey(containingIds)] !== 'undefined' + && objMeta.arraysList.length === 0) return; - // not already placed as to-many relation in container - obj = objMeta.cache[createCompositeKey(values)]; } else { // don't have an object defined for this yet, create it and set the cache obj = {}; objMeta.cache[createCompositeKey(values)] = obj; // copy in properties from table data - for (var k = 0; k < objMeta.valueList.length; k++) { - var cell = objMeta.valueList[k]; - var cellValue = row[cell.column]; - if (cellValue !== null) { - var valueTypeFunction = void 0; - if (isFunction(cell.type)) { - valueTypeFunction = cell.type; - } - else if (typeof cell.type === 'string') { - valueTypeFunction = _this.typeHandlers[cell.type]; + for (var _b = 0, _c = objMeta.valueList; _b < _c.length; _b++) { + var prop = _c[_b]; + if (prop.array === true) + continue; + var cellValue = _this.computeActualCellValue(prop, row[prop.column]); + obj[prop.prop] = cellValue; + } + // Add array values + if (objMeta.arraysList.length > 0) { + for (var _d = 0, _e = objMeta.arraysList; _d < _e.length; _d++) { + var prop = _e[_d]; + var cellValue = _this.computeActualCellValue(prop, row[prop.column]); + if (isArray(obj[prop.prop])) { + obj[prop.prop].push(cellValue); } - if (valueTypeFunction) { - cellValue = valueTypeFunction(cellValue); + else { + obj[prop.prop] = [cellValue]; } } - else if (typeof cell.default !== 'undefined') { - cellValue = cell.default; - } - obj[cell.prop] = cellValue; } // initialize empty to-many relations, they will be populated when // those objects build themselves and find this containing object @@ -241,6 +270,7 @@ var NestHydrationJS; var objMeta = { valueList: [], toOneList: [], + arraysList: [], toManyPropList: [], containingColumn: containingColumn, ownProp: ownProp, @@ -257,18 +287,25 @@ var NestHydrationJS; prop: prop, column: structPropToColumnMap[prop], type: undefined, - default: undefined + default: undefined, + array: undefined }); } else if (structPropToColumnMap[prop].column) { // value property var definitionColumn = structPropToColumnMap[prop]; - objMeta.valueList.push({ + var metaValueProps = { prop: prop, column: definitionColumn.column, type: definitionColumn.type, - default: definitionColumn.default - }); + default: definitionColumn.default, + array: definitionColumn.array === true + }; + objMeta.valueList.push(metaValueProps); + // Add this column to our array list if necessary + if (definitionColumn.array === true) { + objMeta.arraysList.push(metaValueProps); + } } else if (isArray(structPropToColumnMap[prop])) { // list of objects / to-many relation diff --git a/src/NestHydrationJS.ts b/src/NestHydrationJS.ts index c73ccfe..d9def10 100644 --- a/src/NestHydrationJS.ts +++ b/src/NestHydrationJS.ts @@ -1,6 +1,7 @@ 'use strict'; import { create } from "domain"; +import { Z_DEFAULT_COMPRESSION } from "zlib"; const isArray = require('lodash.isarray'); const isFunction = require('lodash.isfunction'); @@ -21,7 +22,8 @@ module NestHydrationJS { prop: string, column: string, type?: string | TypeHandler, - default?: any + default?: any, + array?: boolean } interface Dictionary { @@ -31,6 +33,7 @@ module NestHydrationJS { interface MetaColumnData { valueList: Array, toOneList: Array, + arraysList: Array, toManyPropList: Array, containingColumn: Array | null, ownProp: string | null, @@ -49,7 +52,8 @@ module NestHydrationJS { column: string, id?: boolean, default?: any, - type?: string + type?: string, + array?: boolean } interface Definition { @@ -77,6 +81,26 @@ module NestHydrationJS { } as TypeHandlers; private struct: object | Array | null = null + + private computeActualCellValue(props: MetaValueProps, initialValue: any) { + let cellValue = initialValue; + if (cellValue !== null) { + let valueTypeFunction: TypeHandler | undefined; + + if (isFunction(props.type)) { + valueTypeFunction = props.type as TypeHandler; + } else if (typeof props.type === 'string') { + valueTypeFunction = this.typeHandlers[props.type]; + } + + if (valueTypeFunction) { + cellValue = valueTypeFunction(cellValue); + } + } else if (typeof props.default !== 'undefined') { + cellValue = props.default; + } + return cellValue; + } /* Creates a data structure containing nested objects and/or arrays from * tabular data based on a structure definition provided by @@ -147,8 +171,6 @@ module NestHydrationJS { // Obj is the actual object that will end up in the final structure let obj: Data; - - if (verbose) console.log(meta); // Get all of the values for each id let values: Array = idColumns.map(column => row[column]); @@ -170,47 +192,55 @@ module NestHydrationJS { // check if object already exists in cache if (typeof objMeta.cache[createCompositeKey(values)] !== 'undefined') { - + + // not already placed as to-many relation in container + obj = objMeta.cache[createCompositeKey(values)]; + + // Add array values if necessary + if (objMeta.arraysList.length > 0) { + for (let prop of objMeta.arraysList) { + let cellValue = this.computeActualCellValue(prop, row[prop.column]) + if (isArray(obj[prop.prop])) { + obj[prop.prop].push(cellValue) + } + } + } + if (objMeta.containingIdUsage === null) return; // We know for certain that containing column is set if // containingIdUsage is not null and can cast it as a string // check and see if this has already been linked to the parent, + // doesn't contain any array lists, // and if so we don't need to continue let containingIds = (>objMeta.containingColumn).map(column => row[column]); if (typeof objMeta.containingIdUsage[createCompositeKey(values)] !== 'undefined' && typeof objMeta.containingIdUsage[createCompositeKey(values)][createCompositeKey(containingIds)] !== 'undefined' ) return; - // not already placed as to-many relation in container - obj = objMeta.cache[createCompositeKey(values)]; } else { // don't have an object defined for this yet, create it and set the cache obj = {}; objMeta.cache[createCompositeKey(values)] = obj; // copy in properties from table data - for (let k = 0; k < objMeta.valueList.length; k++) { - let cell = objMeta.valueList[k]; - let cellValue = row[cell.column]; - if (cellValue !== null) { - let valueTypeFunction: TypeHandler | undefined; - - if (isFunction(cell.type)) { - valueTypeFunction = cell.type as TypeHandler; - } else if (typeof cell.type === 'string') { - valueTypeFunction = this.typeHandlers[cell.type]; - } - - if (valueTypeFunction) { - cellValue = valueTypeFunction(cellValue); + for (let prop of objMeta.valueList) { + if (prop.array === true) continue; + let cellValue = this.computeActualCellValue(prop, row[prop.column]); + obj[prop.prop] = cellValue; + } + + // Add array values + if (objMeta.arraysList.length > 0) { + for (let prop of objMeta.arraysList) { + let cellValue = this.computeActualCellValue(prop, row[prop.column]); + if (isArray(obj[prop.prop])) { + obj[prop.prop].push(cellValue) + } else { + obj[prop.prop] = [cellValue] } - } else if (typeof cell.default !== 'undefined') { - cellValue = cell.default; } - - obj[cell.prop] = cellValue; } // initialize empty to-many relations, they will be populated when @@ -336,6 +366,7 @@ module NestHydrationJS { let objMeta: MetaColumnData = { valueList: [], toOneList: [], + arraysList: [], toManyPropList: [], containingColumn: containingColumn, ownProp: ownProp, @@ -353,17 +384,24 @@ module NestHydrationJS { prop: prop, column: structPropToColumnMap[prop] as string, type: undefined, - default: undefined + default: undefined, + array: undefined }); } else if ((structPropToColumnMap[prop]).column) { // value property const definitionColumn = structPropToColumnMap[prop] - objMeta.valueList.push({ + const metaValueProps = { prop: prop, column: definitionColumn.column, type: definitionColumn.type, - default: definitionColumn.default - }); + default: definitionColumn.default, + array: definitionColumn.array === true + } + objMeta.valueList.push(metaValueProps); + // Add this column to our array list if necessary + if (definitionColumn.array === true) { + objMeta.arraysList.push(metaValueProps); + } } else if (isArray(structPropToColumnMap[prop])) { // list of objects / to-many relation objMeta.toManyPropList.push(prop); From 2cdc7817d593c627b55cd8a8f03c5bc47b9df03e Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Thu, 7 Mar 2019 15:29:30 -0800 Subject: [PATCH 13/25] update documentation --- README.md | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8f5870a..3396df8 100644 --- a/README.md +++ b/README.md @@ -226,7 +226,7 @@ Additional Definition Object Capabilities Ids That Aren't First In Definition Properties ---------------------------------------------- -It is possible to specify an id column for mapping to objects instead of having it default to the first property of each object specified in the definition. If multiple properties for an object are specified to be ids only the first will be used. +It is possible to specify the id columns for mapping to objects instead of having it default to the first property of each object specified in the definition. If multiple id properties for an object are specified, they will be treated as a composite key. ```javascript var NestHydrationJS = require('nesthydrationjs')(); @@ -291,6 +291,41 @@ result = NestHydrationJS.nest(table, definition); */ ``` +Arrays +------ + +You can specify that a column should be joined as an array by setting `array` to true. + +### Example + +```javascript +var NestHydrationJS = require('nesthydrationjs')(); + +var table = [ + { id: 1, foo: 'foo' }, + { id: 1, foo: 'bar' }, + { id: 1, foo: 'baz' }, + { id: 2, foo: 'foo' } +]; +var definition = [{ + id: 'id', + title: {column: 'foo', array: true}, +}]; +result = NestHydrationJS.nest(table, definition); +/* result would be the following: +[ + { + id: 1, + title: ['foo', 'bar', 'baz'] + }, + { + id: 2, + title: ['foo'] + } +] +*/ +``` + Custom Type Definition ---------------------- From dce55606b186760969196340c86b88ff6a90d534 Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Thu, 7 Mar 2019 15:54:35 -0800 Subject: [PATCH 14/25] fix git ignore, increment version number, fix existing tests --- .gitignore | 17 +- README.md | 8 +- lib/NestHydrationJS.d.ts | 29 -- lib/NestHydrationJS.js | 457 ------------------ package.json | 5 +- spec/NestHydrationJS/buildMeta.spec.js | 17 + ...ructPropToColumnMapFromColumnHints.spec.js | 2 +- src/NestHydrationJS.ts | 48 +- 8 files changed, 66 insertions(+), 517 deletions(-) delete mode 100644 lib/NestHydrationJS.d.ts delete mode 100644 lib/NestHydrationJS.js diff --git a/.gitignore b/.gitignore index 72237a3..b0ab649 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,15 @@ -node_modules/ -test.js \ No newline at end of file + +* + +!/spec +!/spec/** +!/.coveralls.yml +!/.editorconfig +!/.eslintrc +!/.travis.yml +!/CHANGELOG.md +!/NestHydrationJS.js +!/package-lock.json +!/package.json +!/README.md +!/src/** diff --git a/README.md b/README.md index 3396df8..28bfe32 100644 --- a/README.md +++ b/README.md @@ -223,8 +223,8 @@ Result Additional Definition Object Capabilities ========================================= -Ids That Aren't First In Definition Properties ----------------------------------------------- +Ids That Aren't First In Definition Properties / Composite Ids +-------------------------------------------------------------- It is possible to specify the id columns for mapping to objects instead of having it default to the first property of each object specified in the definition. If multiple id properties for an object are specified, they will be treated as a composite key. @@ -386,6 +386,10 @@ result = NestHydrationJS.nest(table, definition); */ ``` +### Typescript Definitions + +Typescript definitions are bundled with this module. + Related Projects ---------------- diff --git a/lib/NestHydrationJS.d.ts b/lib/NestHydrationJS.d.ts deleted file mode 100644 index 1542b23..0000000 --- a/lib/NestHydrationJS.d.ts +++ /dev/null @@ -1,29 +0,0 @@ -declare module NestHydrationJS { - interface TypeHandler { - (cellValue: any): any; - } - interface Dictionary { - [index: string]: TValue; - } - interface DefinitionColumn { - column: string; - id?: boolean; - default?: any; - type?: string; - array?: boolean; - } - interface Definition { - [index: string]: DefinitionColumn | string | Definition | Definition[]; - } - class NestHydrationJS { - private typeHandlers; - private struct; - private computeActualCellValue; - nest(data: any, structPropToColumnMap: Definition | Definition[] | null | boolean, verbose?: boolean): any; - private buildMeta; - structPropToColumnMapFromColumnHints(columnList: Array, renameMapping?: Dictionary): any; - registerType(name: string, handler: TypeHandler): void; - } -} -declare const _default: () => NestHydrationJS.NestHydrationJS; -export = _default; diff --git a/lib/NestHydrationJS.js b/lib/NestHydrationJS.js deleted file mode 100644 index b565475..0000000 --- a/lib/NestHydrationJS.js +++ /dev/null @@ -1,457 +0,0 @@ -'use strict'; -var isArray = require('lodash.isarray'); -var isFunction = require('lodash.isfunction'); -var keys = require('lodash.keys'); -var values = require('lodash.values'); -var isPlainObject = require('lodash.isplainobject'); -var NestHydrationJS; -(function (NestHydrationJS_1) { - function createCompositeKey(values, separator) { - if (separator === void 0) { separator = ', '; } - return values.join(separator); - } - var NestHydrationJS = /** @class */ (function () { - function NestHydrationJS() { - this.typeHandlers = { - NUMBER: function (cellValue) { - return parseFloat(cellValue); - }, - BOOLEAN: function (cellValue) { - return cellValue == true; - } - }; - this.struct = null; - } - NestHydrationJS.prototype.computeActualCellValue = function (props, initialValue) { - var cellValue = initialValue; - if (cellValue !== null) { - var valueTypeFunction = void 0; - if (isFunction(props.type)) { - valueTypeFunction = props.type; - } - else if (typeof props.type === 'string') { - valueTypeFunction = this.typeHandlers[props.type]; - } - if (valueTypeFunction) { - cellValue = valueTypeFunction(cellValue); - } - } - else if (typeof props.default !== 'undefined') { - cellValue = props.default; - } - return cellValue; - }; - /* Creates a data structure containing nested objects and/or arrays from - * tabular data based on a structure definition provided by - * structPropToColumnMap. If structPropToColumnMap is not provided but - * the data has column names that follow a particular convention then a - * nested structures can also be created. - */ - NestHydrationJS.prototype.nest = function (data, structPropToColumnMap, verbose) { - var _this = this; - if (verbose === void 0) { verbose = false; } - var table; - // VALIDATE PARAMS AND BASIC INITIALIZATION - // Determines that on no results, and empty list is used instead of null. // NOTE: fact check this - var listOnEmpty = false; - if (typeof structPropToColumnMap === 'undefined') { - structPropToColumnMap = null; - } - if (data === null) { - return null; - } - if (!isArray(structPropToColumnMap) && !isPlainObject(structPropToColumnMap) && structPropToColumnMap !== null && structPropToColumnMap !== true) { - throw new Error('nest expects param structPropToColumnMap to be an array, plain object, null, or true'); - } - if (isPlainObject(data)) { - // internal table should be a table format but a plain object - // could be passed as the first (and only) row of that table - table = [data]; - } - else if (isArray(data)) { - table = data; - } - else { - throw Error('nest expects param data to be in the form of a plain object or an array of plain objects (forming a table)'); - } - // structPropToColumnMap can be set to true as a tie break between - // returning null (empty structure) or an empty list - if (structPropToColumnMap === true) { - listOnEmpty = true; - structPropToColumnMap = null; - } - if (structPropToColumnMap === null && table.length > 0) { - // property mapping not specified, determine it from column names - structPropToColumnMap = this.structPropToColumnMapFromColumnHints(keys(table[0])); - } - if (structPropToColumnMap === null) { - // properties is empty, can't form structure or determine content - // for a list. Assume a structure unless listOnEmpty - return listOnEmpty ? [] : null; - } - else if (table.length === 0) { - // table is empty, return the appropriate empty result based on input definition - return isArray(structPropToColumnMap) ? [] : null; - } - // COMPLETE VALIDATING PARAMS AND BASIC INITIALIZATION - var meta = this.buildMeta(structPropToColumnMap, verbose); - // BUILD FROM TABLE - // defines function that can be called recursively - var _nest = function (row, idColumns) { - // Obj is the actual object that will end up in the final structure - var obj; - // Get all of the values for each id - var values = idColumns.map(function (column) { return row[column]; }); - // only really concerned with the meta data for this identity column - var objMeta = meta.idMap[createCompositeKey(idColumns)]; - // If any of the values are null, we'll check and see if we need to set defaults - values = values.map(function (value, idx) { - if (value === null) { - if (objMeta.defaults[idColumns[idx]] !== null && typeof objMeta.defaults[idColumns[idx]] !== 'undefined') { - return objMeta.defaults[idColumns[idx]]; - } - } - return value; - }); - if (values.includes(null)) - return; - // check if object already exists in cache - if (typeof objMeta.cache[createCompositeKey(values)] !== 'undefined') { - // not already placed as to-many relation in container - obj = objMeta.cache[createCompositeKey(values)]; - // Add array values - if (objMeta.arraysList.length > 0) { - for (var _i = 0, _a = objMeta.arraysList; _i < _a.length; _i++) { - var prop = _a[_i]; - var cellValue = _this.computeActualCellValue(prop, row[prop.column]); - if (isArray(obj[prop.prop])) { - obj[prop.prop].push(cellValue); - } - } - } - if (objMeta.containingIdUsage === null) - return; - // We know for certain that containing column is set if - // containingIdUsage is not null and can cast it as a string - // check and see if this has already been linked to the parent, - // doesn't contain any array lists, - // and if so we don't need to continue - var containingIds = objMeta.containingColumn.map(function (column) { return row[column]; }); - if (typeof objMeta.containingIdUsage[createCompositeKey(values)] !== 'undefined' - && typeof objMeta.containingIdUsage[createCompositeKey(values)][createCompositeKey(containingIds)] !== 'undefined' - && objMeta.arraysList.length === 0) - return; - } - else { - // don't have an object defined for this yet, create it and set the cache - obj = {}; - objMeta.cache[createCompositeKey(values)] = obj; - // copy in properties from table data - for (var _b = 0, _c = objMeta.valueList; _b < _c.length; _b++) { - var prop = _c[_b]; - if (prop.array === true) - continue; - var cellValue = _this.computeActualCellValue(prop, row[prop.column]); - obj[prop.prop] = cellValue; - } - // Add array values - if (objMeta.arraysList.length > 0) { - for (var _d = 0, _e = objMeta.arraysList; _d < _e.length; _d++) { - var prop = _e[_d]; - var cellValue = _this.computeActualCellValue(prop, row[prop.column]); - if (isArray(obj[prop.prop])) { - obj[prop.prop].push(cellValue); - } - else { - obj[prop.prop] = [cellValue]; - } - } - } - // initialize empty to-many relations, they will be populated when - // those objects build themselves and find this containing object - for (var k = 0; k < objMeta.toManyPropList.length; k++) { - obj[objMeta.toManyPropList[k]] = []; - } - // initialize null to-one relations and then recursively build them - for (var k = 0; k < objMeta.toOneList.length; k++) { - obj[objMeta.toOneList[k].prop] = null; - _nest(row, [objMeta.toOneList[k].column]); - } - } - // link from the parent - if (objMeta.containingColumn === null) { - // parent is the top level - if (objMeta.isOneOfMany) { - // it is an array - if (_this.struct === null) { - _this.struct = []; - } - _this.struct.push(obj); - } - else { - // it is this object - _this.struct = obj; - } - } - else { - var containingIds = objMeta.containingColumn.map(function (column) { return row[column]; }); - var container = meta.idMap[createCompositeKey(objMeta.containingColumn)].cache[createCompositeKey(containingIds)]; - // If a container exists, it must not be a root, and thus there should - // be an ownProp set - if (container) { - if (objMeta.isOneOfMany) { - // it is an array - container[objMeta.ownProp].push(obj); - } - else { - // it is this object - container[objMeta.ownProp] = obj; - } - } - // record the containing id so we don't do this again (return in earlier - // part of this method) - var containingIdUsage = objMeta.containingIdUsage; - if (typeof (containingIdUsage)[createCompositeKey(values)] === 'undefined') { - containingIdUsage[createCompositeKey(values)] = {}; - } - containingIdUsage[createCompositeKey(values)][createCompositeKey(containingIds)] = true; - } - }; - // struct is populated inside the build function - this.struct = null; - if (verbose) - console.log(meta); - for (var i = 0; i < table.length; i++) { - // go through each row of the table - var row = table[i]; - for (var j = 0; j < meta.primeIdColumnList.length; j++) { - // for each prime id column (corresponding to a to-many relation or - // the top level) attempted to build an object - var primeIdColumn = meta.primeIdColumnList[j]; - _nest(row, primeIdColumn); - } - } - return this.struct; - }; - ; - /* Create a data structure that contains lookups and cache spaces for quick - * reference and action for the workings of the nest method. - */ - NestHydrationJS.prototype.buildMeta = function (structPropToColumnMap, verbose) { - if (verbose === void 0) { verbose = false; } - var meta; - // internally defines recursive function with extra param. This allows cleaner API - var _buildMeta = function (structPropToColumnMap, isOneOfMany, containingColumn, ownProp) { - // var idProp: string | undefined, subIdColumn; - var idProps = []; - var idColumns = []; - var propList = keys(structPropToColumnMap); - if (propList.length === 0) { - throw new Error('invalid structPropToColumnMap format - property \'' + ownProp + '\' can not be an empty array'); - } - for (var i = 0; i < propList.length; i++) { - var prop = propList[i]; - if (structPropToColumnMap[prop].id === true) { - // idProp = prop; - idProps.push(prop); - } - } - if (idProps.length === 0) { - idProps.push(propList[0]); - } - idColumns = idProps.map(function (prop) { return structPropToColumnMap[prop].column || structPropToColumnMap[prop]; }); - if (isOneOfMany) { - meta.primeIdColumnList.push(idColumns); - } - var defaults = {}; - idProps.forEach(function (prop) { - defaults[prop] = typeof structPropToColumnMap[prop].default === 'undefined' ? null : structPropToColumnMap[prop].default; - }); - var objMeta = { - valueList: [], - toOneList: [], - arraysList: [], - toManyPropList: [], - containingColumn: containingColumn, - ownProp: ownProp, - isOneOfMany: isOneOfMany === true, - cache: {}, - containingIdUsage: containingColumn === null ? null : {}, - defaults: defaults - }; - for (var i = 0; i < propList.length; i++) { - var prop = propList[i]; - if (typeof structPropToColumnMap[prop] === 'string') { - // value property - objMeta.valueList.push({ - prop: prop, - column: structPropToColumnMap[prop], - type: undefined, - default: undefined, - array: undefined - }); - } - else if (structPropToColumnMap[prop].column) { - // value property - var definitionColumn = structPropToColumnMap[prop]; - var metaValueProps = { - prop: prop, - column: definitionColumn.column, - type: definitionColumn.type, - default: definitionColumn.default, - array: definitionColumn.array === true - }; - objMeta.valueList.push(metaValueProps); - // Add this column to our array list if necessary - if (definitionColumn.array === true) { - objMeta.arraysList.push(metaValueProps); - } - } - else if (isArray(structPropToColumnMap[prop])) { - // list of objects / to-many relation - objMeta.toManyPropList.push(prop); - _buildMeta(structPropToColumnMap[prop][0], true, idColumns, prop); - } - else if (isPlainObject(structPropToColumnMap[prop])) { - // object / to-one relation - var subIdColumn = values(structPropToColumnMap[prop])[0]; - if (typeof subIdColumn === 'undefined') { - throw new Error('invalid structPropToColumnMap format - property \'' + prop + '\' can not be an empty object'); - } - if (subIdColumn.column) { - subIdColumn = subIdColumn.column; - } - objMeta.toOneList.push({ - prop: prop, - column: subIdColumn - }); - _buildMeta(structPropToColumnMap[prop], false, idColumns, prop); - } - else { - throw new Error('invalid structPropToColumnMap format - property \'' + prop + '\' must be either a string, a plain object or an array'); - } - } - meta.idMap[createCompositeKey(idColumns)] = objMeta; - }; - // this data structure is populated by the _buildMeta function - meta = { - primeIdColumnList: [], - idMap: {} - }; - if (isArray(structPropToColumnMap)) { - if (structPropToColumnMap.length !== 1) { - throw new Error('invalid structPropToColumnMap format - can not have multiple roots for structPropToColumnMap, if an array it must only have one item'); - } - // call with first object, but inform _buildMeta it is an array - _buildMeta(structPropToColumnMap[0], true, null, null); - } - else if (isPlainObject(structPropToColumnMap)) { - // register first column as prime id column - var columns = values(structPropToColumnMap); - if (columns.length === 0) { - throw new Error('invalid structPropToColumnMap format - the base object can not be an empty object'); - } - // First determine if there are any keys set on the columns - var keys_1 = columns.reduce(function (accumulator, column) { - if (column.id === true) { - accumulator.push(column.column); - } - return accumulator; - }, []); - // If there were no keys set, then take the first column as the id - if (keys_1.length === 0) { - if (typeof columns[0] === 'string') { - keys_1.push(columns[0]); - } - else if (typeof columns[0].column === 'string') { - keys_1.push(columns[0].column); - } - } - meta.primeIdColumnList.push(keys_1); - // construct the rest - _buildMeta(structPropToColumnMap, false, null, null); - } - return meta; - }; - ; - /* Returns a property mapping data structure based on the names of columns - * in columnList. Used internally by nest when its propertyMapping param - * is not specified. - * - */ - NestHydrationJS.prototype.structPropToColumnMapFromColumnHints = function (columnList, renameMapping) { - if (typeof renameMapping === 'undefined') { - renameMapping = {}; - } - var propertyMapping = { base: null }; - for (var i = 0; i < columnList.length; i++) { - var column = columnList[i]; - var columnType = column.split('___'); - var type = null; - var isId = false; - for (var j = 1; j < columnType.length; j++) { - if (columnType[j] === 'ID') { - isId = true; - } - else if (typeof this.typeHandlers[columnType[j]] !== 'undefined') { - type = columnType[j]; - } - } - var pointer = propertyMapping; // point to base on each new column - var prop = 'base'; - var navList = columnType[0].split('_'); - for (var j = 0; j < navList.length; j++) { - var nav = navList[j]; - if (nav === '') { - if (pointer[prop] === null) { - pointer[prop] = [null]; - } - pointer = pointer[prop]; - prop = 0; - } - else { - if (pointer[prop] === null) { - pointer[prop] = {}; - } - if (typeof pointer[prop][nav] === 'undefined') { - var renamedColumn = typeof renameMapping[column] === 'undefined' - ? column - : renameMapping[column]; - if (type !== null || isId) { - // no longer a simple mapping, has need of the type or id properties - renamedColumn = { column: renamedColumn }; - } - if (type !== null) { - // detail the type in the column map if type provided - renamedColumn.type = type; - } - if (isId) { - // set the id property in the column map - renamedColumn.id = true; - } - pointer[prop][nav] = j === (navList.length - 1) - ? renamedColumn // is leaf node, store full column string - : null // iteration will replace with object or array - ; - } - pointer = pointer[prop]; - prop = nav; - } - } - } - return propertyMapping.base; - }; - ; - /* Registers a custom type handler */ - NestHydrationJS.prototype.registerType = function (name, handler) { - if (this.typeHandlers[name]) { - throw new Error('Handler with type, ' + name + ', already exists'); - } - this.typeHandlers[name] = handler; - }; - ; - return NestHydrationJS; - }()); - NestHydrationJS_1.NestHydrationJS = NestHydrationJS; -})(NestHydrationJS || (NestHydrationJS = {})); -module.exports = function generate() { return new NestHydrationJS.NestHydrationJS(); }; diff --git a/package.json b/package.json index 47f1953..43ade50 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nesthydrationjs", - "version": "1.0.6", + "version": "1.1.0", "description": "Provides nested objects from tabular data.", "keywords": [ "Hydration", @@ -17,7 +17,8 @@ "coverage": "nyc jasmine", "coveralls": "nyc report --reporter=text-lcov | coveralls", "test": "jasmine", - "lint": "eslint NestHydrateJS.js spec/**/*.js" + "lint": "eslint NestHydrateJS.js spec/**/*.js", + "build": "tsc" }, "repository": { "type": "git", diff --git a/spec/NestHydrationJS/buildMeta.spec.js b/spec/NestHydrationJS/buildMeta.spec.js index a85a245..faebb75 100644 --- a/spec/NestHydrationJS/buildMeta.spec.js +++ b/spec/NestHydrationJS/buildMeta.spec.js @@ -23,6 +23,7 @@ describe('NestHydrationJS', function () { ], toOneList: [], toManyPropList: [], + arraysList: [], containingColumn: null, ownProp: null, isOneOfMany: false, @@ -55,6 +56,7 @@ describe('NestHydrationJS', function () { ], toOneList: [], toManyPropList: [], + arraysList: [], containingColumn: null, ownProp: null, isOneOfMany: false, @@ -87,6 +89,7 @@ describe('NestHydrationJS', function () { ], toOneList: [], toManyPropList: [], + arraysList: [], containingColumn: null, ownProp: null, isOneOfMany: false, @@ -119,6 +122,7 @@ describe('NestHydrationJS', function () { ], toOneList: [], toManyPropList: [], + arraysList: [], containingColumn: null, ownProp: null, isOneOfMany: false, @@ -153,6 +157,7 @@ describe('NestHydrationJS', function () { ], toOneList: [], toManyPropList: [], + arraysList: [], containingColumn: null, ownProp: null, isOneOfMany: false, @@ -187,6 +192,7 @@ describe('NestHydrationJS', function () { ], toOneList: [], toManyPropList: [], + arraysList: [], containingColumn: null, ownProp: null, isOneOfMany: false, @@ -223,6 +229,7 @@ describe('NestHydrationJS', function () { ], toOneList: [], toManyPropList: [], + arraysList: [], containingColumn: ['id'], ownProp: 'a', isOneOfMany: false, @@ -237,6 +244,7 @@ describe('NestHydrationJS', function () { ], toOneList: [{prop: 'a', column: 'c'}], toManyPropList: [], + arraysList: [], containingColumn: null, ownProp: null, isOneOfMany: false, @@ -271,6 +279,7 @@ describe('NestHydrationJS', function () { ], toOneList: [], toManyPropList: [], + arraysList: [], containingColumn: null, ownProp: null, isOneOfMany: true, @@ -305,6 +314,7 @@ describe('NestHydrationJS', function () { ], toOneList: [], toManyPropList: [], + arraysList: [], containingColumn: null, ownProp: null, isOneOfMany: true, @@ -339,6 +349,7 @@ describe('NestHydrationJS', function () { ], toOneList: [], toManyPropList: [], + arraysList: [], containingColumn: null, ownProp: null, isOneOfMany: true, @@ -373,6 +384,7 @@ describe('NestHydrationJS', function () { ], toOneList: [], toManyPropList: [], + arraysList: [], containingColumn: null, ownProp: null, isOneOfMany: true, @@ -407,6 +419,7 @@ describe('NestHydrationJS', function () { ], toOneList: [], toManyPropList: [], + arraysList: [], containingColumn: null, ownProp: null, isOneOfMany: true, @@ -441,6 +454,7 @@ describe('NestHydrationJS', function () { ], toOneList: [], toManyPropList: [], + arraysList: [], containingColumn: null, ownProp: null, isOneOfMany: true, @@ -486,6 +500,7 @@ describe('NestHydrationJS', function () { toManyPropList: [ 'e' ], + arraysList: [], containingColumn: null, ownProp: null, isOneOfMany: true, @@ -499,6 +514,7 @@ describe('NestHydrationJS', function () { ], toOneList: [], toManyPropList: [], + arraysList: [], containingColumn: ['_a'], ownProp: 'c', isOneOfMany: false, @@ -513,6 +529,7 @@ describe('NestHydrationJS', function () { ], toOneList: [], toManyPropList: [], + arraysList: [], containingColumn: ['_a'], ownProp: 'e', isOneOfMany: true, diff --git a/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js b/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js index 2c1651d..e474182 100644 --- a/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js +++ b/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js @@ -84,7 +84,7 @@ describe('NestHydrationJS', function () { }); }); - describe('passed two properties as columnList both as id columns', function () { + describe('passed two properties both as id columns', function () { var result; beforeEach(function () { var columnList = [ diff --git a/src/NestHydrationJS.ts b/src/NestHydrationJS.ts index d9def10..4ea3e77 100644 --- a/src/NestHydrationJS.ts +++ b/src/NestHydrationJS.ts @@ -22,8 +22,7 @@ module NestHydrationJS { prop: string, column: string, type?: string | TypeHandler, - default?: any, - array?: boolean + default?: any } interface Dictionary { @@ -197,12 +196,12 @@ module NestHydrationJS { obj = objMeta.cache[createCompositeKey(values)]; // Add array values if necessary - if (objMeta.arraysList.length > 0) { - for (let prop of objMeta.arraysList) { - let cellValue = this.computeActualCellValue(prop, row[prop.column]) - if (isArray(obj[prop.prop])) { - obj[prop.prop].push(cellValue) - } + for (let prop of objMeta.arraysList) { + let cellValue = this.computeActualCellValue(prop, row[prop.column]) + if (isArray(obj[prop.prop])) { + obj[prop.prop].push(cellValue) + } else { + obj[prop.prop] = [cellValue] } } @@ -212,7 +211,6 @@ module NestHydrationJS { // containingIdUsage is not null and can cast it as a string // check and see if this has already been linked to the parent, - // doesn't contain any array lists, // and if so we don't need to continue let containingIds = (>objMeta.containingColumn).map(column => row[column]); if (typeof objMeta.containingIdUsage[createCompositeKey(values)] !== 'undefined' @@ -226,20 +224,17 @@ module NestHydrationJS { // copy in properties from table data for (let prop of objMeta.valueList) { - if (prop.array === true) continue; let cellValue = this.computeActualCellValue(prop, row[prop.column]); obj[prop.prop] = cellValue; } // Add array values - if (objMeta.arraysList.length > 0) { - for (let prop of objMeta.arraysList) { - let cellValue = this.computeActualCellValue(prop, row[prop.column]); - if (isArray(obj[prop.prop])) { - obj[prop.prop].push(cellValue) - } else { - obj[prop.prop] = [cellValue] - } + for (let prop of objMeta.arraysList) { + let cellValue = this.computeActualCellValue(prop, row[prop.column]); + if (isArray(obj[prop.prop])) { + obj[prop.prop].push(cellValue) + } else { + obj[prop.prop] = [cellValue] } } @@ -384,9 +379,7 @@ module NestHydrationJS { prop: prop, column: structPropToColumnMap[prop] as string, type: undefined, - default: undefined, - array: undefined - }); + default: undefined }); } else if ((structPropToColumnMap[prop]).column) { // value property const definitionColumn = structPropToColumnMap[prop] @@ -394,13 +387,14 @@ module NestHydrationJS { prop: prop, column: definitionColumn.column, type: definitionColumn.type, - default: definitionColumn.default, - array: definitionColumn.array === true + default: definitionColumn.default } - objMeta.valueList.push(metaValueProps); + // Add this column to our array list if necessary if (definitionColumn.array === true) { objMeta.arraysList.push(metaValueProps); + } else { + objMeta.valueList.push(metaValueProps); } } else if (isArray(structPropToColumnMap[prop])) { // list of objects / to-many relation @@ -498,9 +492,12 @@ module NestHydrationJS { let type = null; let isId = false; + let isArray = false; for (let j = 1; j < columnType.length; j++) { if (columnType[j] === 'ID') { isId = true; + } else if (columnType[j] === 'ARRAY') { + isArray = true; } else if (typeof this.typeHandlers[columnType[j]] !== 'undefined') { type = columnType[j]; } @@ -541,6 +538,9 @@ module NestHydrationJS { // set the id property in the column map renamedColumn.id = true; } + if (isArray) { + renamedColumn.array = true; + } pointer[prop][nav] = j === (navList.length - 1) ? renamedColumn // is leaf node, store full column string : null // iteration will replace with object or array From c7a6bf6875a1fc4b63105adb6fe9ebcd3ff7b1ea Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Thu, 7 Mar 2019 16:42:34 -0800 Subject: [PATCH 15/25] add tslint, fix NestHydrationJS.ts to be compliant with tslint, updated eslint to just run on specs --- .eslintrc | 18 - .gitignore | 2 + package-lock.json | 120 +++++- package.json | 9 +- spec/.eslintrc | 16 + spec/NestHydrationJS/nest.spec.js | 1 - src/NestHydrationJS.ts | 662 +++++++++++++++--------------- 7 files changed, 474 insertions(+), 354 deletions(-) delete mode 100644 .eslintrc diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index ec3f3c4..0000000 --- a/.eslintrc +++ /dev/null @@ -1,18 +0,0 @@ ---- -env: - es6: true - node: true -extends: 'eslint:recommended' -rules: - indent: - - error - - tab - linebreak-style: - - error - - unix - quotes: - - error - - single - semi: - - error - - always diff --git a/.gitignore b/.gitignore index b0ab649..e22b266 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ !/package.json !/README.md !/src/** +!/tsconfig.json +!/tslint.json \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 15bb241..6d9b110 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "nesthydrationjs", - "version": "1.0.6", + "version": "1.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -10,6 +10,51 @@ "integrity": "sha512-9IdED8wU93ty8gP06ninox+42SBSJHp2IAamsSYMUY76mshRTeUsid/gtbl8ovnOwy8im41ib4cxTiIYMXGKew==", "dev": true }, + "@types/lodash.isarray": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/lodash.isarray/-/lodash.isarray-4.0.6.tgz", + "integrity": "sha512-lXLDKJ4jWhtAd6EVQOGHwX8iKJlITY5J+HAgim6oglepJrPXAv7hdoN7hrJSwpOYZ7oEpMng+bPasl4LZWp4GQ==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, + "@types/lodash.isfunction": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/lodash.isfunction/-/lodash.isfunction-3.0.6.tgz", + "integrity": "sha512-olhgKmBgzHnA5pxsOI6YHunzTBMSyBw1XjxIKFio8W+XhYiELGTt05FStE0suV0GWtlIMdn7V8M/UbYbSVdGYw==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, + "@types/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-8G41YFhmOl8Ck6NrwLK5hhnbz6ADfuDJP+zusDnX3PoYhfC60+H/rQE6zmdO4yFzPCPJPY4oGZK2spbXm6gYEA==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, + "@types/lodash.keys": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@types/lodash.keys/-/lodash.keys-4.2.6.tgz", + "integrity": "sha512-FCtZsHapyH/Xu0New7jTLiNwbDfirIgpmii+vndMxM9Guz+kU+uYgKvSaVN4H+CReT6Ht+sfZTrquq0hnSjgxg==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, + "@types/lodash.values": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@types/lodash.values/-/lodash.values-4.3.6.tgz", + "integrity": "sha512-iCudwRNYIRO1ERA58WauveyaWFrO3/fisVQZts+acdLZmJTJABLxCG+NeEUqo+/o45LthyAw/gC5wMjssaGpbQ==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, "@types/node": { "version": "11.10.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-11.10.4.tgz", @@ -211,6 +256,12 @@ "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==", "dev": true }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, "caller-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", @@ -320,6 +371,12 @@ "delayed-stream": "~1.0.0" } }, + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -433,6 +490,12 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -1149,6 +1212,7 @@ "version": "0.1.4", "bundled": true, "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -2331,7 +2395,8 @@ "longest": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "loose-envify": { "version": "1.3.1", @@ -3802,6 +3867,12 @@ "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", "dev": true }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -3932,6 +4003,15 @@ "resolve-from": "^1.0.0" } }, + "resolve": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, "resolve-from": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", @@ -4158,6 +4238,42 @@ "punycode": "^1.4.1" } }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "tslint": { + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.13.1.tgz", + "integrity": "sha512-fplQqb2miLbcPhyHoMV4FU9PtNRbgmm/zI5d3SZwwmJQM6V0eodju+hplpyfhLWpmwrDNfNYU57uYRb8s0zZoQ==", + "dev": true, + "requires": { + "babel-code-frame": "^6.22.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.7.0", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.27.2" + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", diff --git a/package.json b/package.json index 43ade50..1076546 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "coverage": "nyc jasmine", "coveralls": "nyc report --reporter=text-lcov | coveralls", "test": "jasmine", - "lint": "eslint NestHydrateJS.js spec/**/*.js", + "lint-specs": "eslint spec/**/*.js", + "lint": "tslint -c ./tslint.yaml src/**/*.ts", "build": "tsc" }, "repository": { @@ -33,11 +34,17 @@ }, "devDependencies": { "@types/lodash": "^4.14.122", + "@types/lodash.isarray": "^4.0.6", + "@types/lodash.isfunction": "^3.0.6", + "@types/lodash.isplainobject": "^4.0.6", + "@types/lodash.keys": "^4.2.6", + "@types/lodash.values": "^4.3.6", "@types/node": "^11.10.4", "coveralls": "^3.0.1", "eslint": "^4.19.1", "jasmine": "^3.1.0", "nyc": "^11.7.3", + "tslint": "^5.13.1", "typescript": "^3.3.3333" }, "files": [ diff --git a/spec/.eslintrc b/spec/.eslintrc index 5a7a8d5..3abd949 100644 --- a/spec/.eslintrc +++ b/spec/.eslintrc @@ -1,3 +1,19 @@ --- env: + es6: true jasmine: true + node: true +extends: 'eslint:recommended' +rules: + indent: + - error + - tab + linebreak-style: + - error + - unix + quotes: + - error + - single + semi: + - error + - always diff --git a/spec/NestHydrationJS/nest.spec.js b/spec/NestHydrationJS/nest.spec.js index 4f614b2..ce47a4a 100644 --- a/spec/NestHydrationJS/nest.spec.js +++ b/spec/NestHydrationJS/nest.spec.js @@ -449,7 +449,6 @@ describe('NestHydrationJS', function () { it('should match expected structure', function () { var expected = {a: 'a1', b: 'b1'}; - console.log("result: ", result) expect(result).toEqual(expected); }); }); diff --git a/src/NestHydrationJS.ts b/src/NestHydrationJS.ts index 4ea3e77..814f91c 100644 --- a/src/NestHydrationJS.ts +++ b/src/NestHydrationJS.ts @@ -1,133 +1,113 @@ 'use strict'; -import { create } from "domain"; -import { Z_DEFAULT_COMPRESSION } from "zlib"; - -const isArray = require('lodash.isarray'); -const isFunction = require('lodash.isfunction'); -const keys = require('lodash.keys'); -const values = require('lodash.values'); -const isPlainObject = require('lodash.isplainobject'); - -module NestHydrationJS { - interface TypeHandlers { - [index: string]: TypeHandler - } +import isArray = require('lodash.isarray'); +import isFunction = require('lodash.isfunction'); +import isPlainObject = require('lodash.isplainobject'); +import keys = require('lodash.keys'); +import values = require('lodash.values'); + +// tslint:disable-next-line: no-namespace +namespace NestHydrationJS { - interface TypeHandler { - (cellValue: any): any + interface ITypeHandlers { + [index: string]: ITypeHandler; } - - interface MetaValueProps { - prop: string, - column: string, - type?: string | TypeHandler, - default?: any + + type ITypeHandler = (cellValue: any) => any; + + interface IMetaValueProps { + prop: string; + column: string; + type?: string | ITypeHandler; + default?: any; } - - interface Dictionary { + + interface IDictionary { [index: string]: TValue; } - - interface MetaColumnData { - valueList: Array, - toOneList: Array, - arraysList: Array, - toManyPropList: Array, - containingColumn: Array | null, - ownProp: string | null, - isOneOfMany: boolean, - cache: Dictionary, - containingIdUsage: Dictionary> | null, - defaults: Dictionary + + interface IMetaColumnData { + valueList: IMetaValueProps[]; + toOneList: IMetaValueProps[]; + arraysList: IMetaValueProps[]; + toManyPropList: string[]; + containingColumn: string[] | null; + ownProp: string | null; + isOneOfMany: boolean; + cache: IDictionary; + containingIdUsage: IDictionary> | null; + defaults: IDictionary; } - - interface MetaData { - primeIdColumnList: Array>, - idMap: { [index: string]: MetaColumnData } + + interface IMetaData { + primeIdColumnList: string[][]; + idMap: { [index: string]: IMetaColumnData }; } - - interface DefinitionColumn { - column: string, - id?: boolean, - default?: any, - type?: string, - array?: boolean + + interface IDefinitionColumn { + column: string; + id?: boolean; + default?: any; + type?: string; + array?: boolean; } - - interface Definition { - [index: string]: DefinitionColumn | string | Definition | Definition[] + + interface IDefinition { + [index: string]: IDefinitionColumn | string | IDefinition | IDefinition[]; } - - interface Data { - [index: string]: any, - [index: number]: any + + interface IData { + [index: string]: any; + [index: number]: any; } - function createCompositeKey(values: Array, separator = ', '): string { - return values.join(separator); + function createCompositeKey(vals: Array, separator = ', '): string { + return vals.join(separator); } + // tslint:disable-next-line: no-shadowed-variable export class NestHydrationJS { - + private typeHandlers = { - NUMBER: function (cellValue: any) { + NUMBER(cellValue: any) { return parseFloat(cellValue); }, - BOOLEAN: function (cellValue: any) { + BOOLEAN(cellValue: any) { + // tslint:disable-next-line: triple-equals return cellValue == true; - } - } as TypeHandlers; - - private struct: object | Array | null = null + }, + } as ITypeHandlers; + + private struct: object | any[] | null = null; - private computeActualCellValue(props: MetaValueProps, initialValue: any) { - let cellValue = initialValue; - if (cellValue !== null) { - let valueTypeFunction: TypeHandler | undefined; - - if (isFunction(props.type)) { - valueTypeFunction = props.type as TypeHandler; - } else if (typeof props.type === 'string') { - valueTypeFunction = this.typeHandlers[props.type]; - } - - if (valueTypeFunction) { - cellValue = valueTypeFunction(cellValue); - } - } else if (typeof props.default !== 'undefined') { - cellValue = props.default; - } - return cellValue; - } - /* Creates a data structure containing nested objects and/or arrays from * tabular data based on a structure definition provided by * structPropToColumnMap. If structPropToColumnMap is not provided but * the data has column names that follow a particular convention then a * nested structures can also be created. */ - nest(data: any, structPropToColumnMap: Definition | Definition[] | null | boolean, verbose = false): any { - + public nest(data: any, structPropToColumnMap: IDefinition | IDefinition[] | null | boolean, verbose = false): any { + let table; - + // VALIDATE PARAMS AND BASIC INITIALIZATION - + // Determines that on no results, and empty list is used instead of null. // NOTE: fact check this let listOnEmpty = false; - + if (typeof structPropToColumnMap === 'undefined') { structPropToColumnMap = null; } - + if (data === null) { return null; } - - if (!isArray(structPropToColumnMap) && !isPlainObject(structPropToColumnMap) && structPropToColumnMap !== null && structPropToColumnMap !== true) { + + if (!isArray(structPropToColumnMap) && !isPlainObject(structPropToColumnMap) && + structPropToColumnMap !== null && structPropToColumnMap !== true) { throw new Error('nest expects param structPropToColumnMap to be an array, plain object, null, or true'); } - + if (isPlainObject(data)) { // internal table should be a table format but a plain object // could be passed as the first (and only) row of that table @@ -135,21 +115,22 @@ module NestHydrationJS { } else if (isArray(data)) { table = data; } else { - throw Error('nest expects param data to be in the form of a plain object or an array of plain objects (forming a table)'); + // tslint:disable-next-line: max-line-length + throw Error(`nest expects param data to be in the form of a plain object or an array of plain objects (forming a table)`); } - + // structPropToColumnMap can be set to true as a tie break between // returning null (empty structure) or an empty list if (structPropToColumnMap === true) { listOnEmpty = true; structPropToColumnMap = null; } - + if (structPropToColumnMap === null && table.length > 0) { // property mapping not specified, determine it from column names structPropToColumnMap = this.structPropToColumnMapFromColumnHints(keys(table[0])); } - + if (structPropToColumnMap === null) { // properties is empty, can't form structure or determine content // for a list. Assume a structure unless listOnEmpty @@ -158,99 +139,99 @@ module NestHydrationJS { // table is empty, return the appropriate empty result based on input definition return isArray(structPropToColumnMap) ? [] : null; } - + // COMPLETE VALIDATING PARAMS AND BASIC INITIALIZATION - - let meta = this.buildMeta(structPropToColumnMap, verbose); + + const meta = this.buildMeta(structPropToColumnMap as IDefinition | IDefinition[]); // BUILD FROM TABLE - + // defines function that can be called recursively - let _nest = (row: Dictionary, idColumns: string[]) => { - + const recursiveNest = (row: IDictionary, idColumns: string[]) => { + // Obj is the actual object that will end up in the final structure - let obj: Data; - + let obj: IData; + // Get all of the values for each id - let values: Array = idColumns.map(column => row[column]); - + let vals: any[] = idColumns.map((column) => row[column]); + // only really concerned with the meta data for this identity column - let objMeta = meta.idMap[createCompositeKey(idColumns)]; - + const objMeta = meta.idMap[createCompositeKey(idColumns)]; + // If any of the values are null, we'll check and see if we need to set defaults - values = values.map((value, idx) => { + vals = vals.map((value, idx) => { if (value === null) { if (objMeta.defaults[idColumns[idx]] !== null && typeof objMeta.defaults[idColumns[idx]] !== 'undefined') { return objMeta.defaults[idColumns[idx]]; } } return value; - }) + }); + + if (vals.includes(null)) { return; } - if (values.includes(null)) return; - // check if object already exists in cache - if (typeof objMeta.cache[createCompositeKey(values)] !== 'undefined') { + if (typeof objMeta.cache[createCompositeKey(vals)] !== 'undefined') { // not already placed as to-many relation in container - obj = objMeta.cache[createCompositeKey(values)]; + obj = objMeta.cache[createCompositeKey(vals)]; // Add array values if necessary - for (let prop of objMeta.arraysList) { - let cellValue = this.computeActualCellValue(prop, row[prop.column]) + for (const prop of objMeta.arraysList) { + const cellValue = this.computeActualCellValue(prop, row[prop.column]); if (isArray(obj[prop.prop])) { - obj[prop.prop].push(cellValue) + obj[prop.prop].push(cellValue); } else { - obj[prop.prop] = [cellValue] + obj[prop.prop] = [cellValue]; } } - if (objMeta.containingIdUsage === null) return; - + if (objMeta.containingIdUsage === null) { return; } + // We know for certain that containing column is set if // containingIdUsage is not null and can cast it as a string - + // check and see if this has already been linked to the parent, // and if so we don't need to continue - let containingIds = (>objMeta.containingColumn).map(column => row[column]); - if (typeof objMeta.containingIdUsage[createCompositeKey(values)] !== 'undefined' - && typeof objMeta.containingIdUsage[createCompositeKey(values)][createCompositeKey(containingIds)] !== 'undefined' - ) return; - + const containingIds = (objMeta.containingColumn as string[]).map((column) => row[column]); + if (typeof objMeta.containingIdUsage[createCompositeKey(vals)] !== 'undefined' + && typeof objMeta.containingIdUsage[createCompositeKey(vals)][createCompositeKey(containingIds)] !== 'undefined' + ) { return; } + } else { // don't have an object defined for this yet, create it and set the cache obj = {}; - objMeta.cache[createCompositeKey(values)] = obj; - + objMeta.cache[createCompositeKey(vals)] = obj; + // copy in properties from table data - for (let prop of objMeta.valueList) { - let cellValue = this.computeActualCellValue(prop, row[prop.column]); + for (const prop of objMeta.valueList) { + const cellValue = this.computeActualCellValue(prop, row[prop.column]); obj[prop.prop] = cellValue; } // Add array values - for (let prop of objMeta.arraysList) { - let cellValue = this.computeActualCellValue(prop, row[prop.column]); + for (const prop of objMeta.arraysList) { + const cellValue = this.computeActualCellValue(prop, row[prop.column]); if (isArray(obj[prop.prop])) { - obj[prop.prop].push(cellValue) + obj[prop.prop].push(cellValue); } else { - obj[prop.prop] = [cellValue] + obj[prop.prop] = [cellValue]; } } - + // initialize empty to-many relations, they will be populated when // those objects build themselves and find this containing object - for (let k = 0; k < objMeta.toManyPropList.length; k++) { - obj[objMeta.toManyPropList[k]] = []; + for (const prop of objMeta.toManyPropList) { + obj[prop] = []; } - + // initialize null to-one relations and then recursively build them - for (let k = 0; k < objMeta.toOneList.length; k++) { - obj[objMeta.toOneList[k].prop] = null; - _nest(row, [objMeta.toOneList[k].column]); + for (const prop of objMeta.toOneList) { + obj[prop.prop] = null; + recursiveNest(row, [prop.column]); } } - + // link from the parent if (objMeta.containingColumn === null) { // parent is the top level @@ -259,137 +240,245 @@ module NestHydrationJS { if (this.struct === null) { this.struct = []; } - (>this.struct).push(obj); + (this.struct as any[]).push(obj); } else { // it is this object this.struct = obj; } } else { - let containingIds = objMeta.containingColumn.map(column => row[column]); - let container = meta.idMap[createCompositeKey(objMeta.containingColumn)].cache[createCompositeKey(containingIds)]; - + const containingIds = objMeta.containingColumn.map((column) => row[column]); + const container = meta.idMap[createCompositeKey(objMeta.containingColumn)] + .cache[createCompositeKey(containingIds)]; + // If a container exists, it must not be a root, and thus there should // be an ownProp set if (container) { if (objMeta.isOneOfMany) { // it is an array - container[objMeta.ownProp].push(obj); + container[objMeta.ownProp as string].push(obj); } else { // it is this object - container[objMeta.ownProp] = obj; + container[objMeta.ownProp as string] = obj; } } - + // record the containing id so we don't do this again (return in earlier // part of this method) - const containingIdUsage = >>objMeta.containingIdUsage - if (typeof (containingIdUsage)[createCompositeKey(values)] === 'undefined') { - containingIdUsage[createCompositeKey(values)] = {}; + const containingIdUsage = objMeta.containingIdUsage as IDictionary>; + if (typeof (containingIdUsage)[createCompositeKey(vals)] === 'undefined') { + containingIdUsage[createCompositeKey(vals)] = {}; } - containingIdUsage[createCompositeKey(values)][createCompositeKey(containingIds)] = true; + containingIdUsage[createCompositeKey(vals)][createCompositeKey(containingIds)] = true; } }; - + // struct is populated inside the build function this.struct = null; - if (verbose) console.log(meta); - - for (let i = 0; i < table.length; i++) { - // go through each row of the table - let row = table[i]; - - for (let j = 0; j < meta.primeIdColumnList.length; j++) { + // tslint:disable-next-line: no-console + if (verbose) { console.log(meta); } + + for (const row of table) { + for (const primeIdColumn of meta.primeIdColumnList) { // for each prime id column (corresponding to a to-many relation or // the top level) attempted to build an object - let primeIdColumn = meta.primeIdColumnList[j]; - _nest(row, primeIdColumn); + recursiveNest(row, primeIdColumn); } } - + return this.struct; - }; - + } + /* Returns a property mapping data structure based on the names of columns + * in columnList. Used internally by nest when its propertyMapping param + * is not specified. + * + */ + public structPropToColumnMapFromColumnHints(columnList: string[], renameMapping?: IDictionary) { + + if (typeof renameMapping === 'undefined') { + renameMapping = {}; + } + + const propertyMapping: any = {base: null}; + + for (const column of columnList) { + + const columnType = column.split('___'); + + let type = null; + let idFlagSet = false; + let arrayFlagSet = false; + for (let j = 1; j < columnType.length; j++) { + if (columnType[j] === 'ID') { + idFlagSet = true; + } else if (columnType[j] === 'ARRAY') { + arrayFlagSet = true; + } else if (typeof this.typeHandlers[columnType[j]] !== 'undefined') { + type = columnType[j]; + } + } + + let pointer = propertyMapping; // point to base on each new column + let prop: string | number = 'base'; + + const navList = columnType[0].split('_'); + + for (let j = 0; j < navList.length; j++) { + const nav = navList[j]; + + if (nav === '') { + if (pointer[prop] === null) { + pointer[prop] = [null]; + } + pointer = pointer[prop]; + prop = 0; + } else { + if (pointer[prop] === null) { + pointer[prop] = {}; + } + if (typeof pointer[prop][nav] === 'undefined') { + let renamedColumn: any = typeof renameMapping[column] === 'undefined' + ? column + : renameMapping[column] + ; + if (type !== null || idFlagSet) { + // no longer a simple mapping, has need of the type or id properties + renamedColumn = {column: renamedColumn}; + } + if (type !== null) { + // detail the type in the column map if type provided + renamedColumn.type = type; + } + if (idFlagSet) { + // set the id property in the column map + renamedColumn.id = true; + } + if (arrayFlagSet) { + renamedColumn.array = true; + } + pointer[prop][nav] = j === (navList.length - 1) + ? renamedColumn // is leaf node, store full column string + : null // iteration will replace with object or array + ; + } + pointer = pointer[prop]; + prop = nav; + } + } + } + + return propertyMapping.base; + } + /* Registers a custom type handler */ + public registerType(name: string, handler: ITypeHandler) { + if (this.typeHandlers[name]) { + throw new Error('Handler with type, ' + name + ', already exists'); + } + + this.typeHandlers[name] = handler; + } + private computeActualCellValue(props: IMetaValueProps, initialValue: any) { + let cellValue = initialValue; + if (cellValue !== null) { + let valueTypeFunction: ITypeHandler | undefined; + + if (isFunction(props.type)) { + valueTypeFunction = props.type as ITypeHandler; + } else if (typeof props.type === 'string') { + valueTypeFunction = this.typeHandlers[props.type]; + } + + if (valueTypeFunction) { + cellValue = valueTypeFunction(cellValue); + } + } else if (typeof props.default !== 'undefined') { + cellValue = props.default; + } + return cellValue; + } + /* Create a data structure that contains lookups and cache spaces for quick * reference and action for the workings of the nest method. */ - private buildMeta(structPropToColumnMap: Definition | Definition[], verbose = false): MetaData { - - var meta: MetaData; - - // internally defines recursive function with extra param. This allows cleaner API - let _buildMeta = function( - structPropToColumnMap: Definition, - isOneOfMany: boolean, - containingColumn: Array | null, - ownProp: string | null) - { - // var idProp: string | undefined, subIdColumn; - - let idProps = []; + private buildMeta(structPropToColumnMap: IDefinition | IDefinition[]): IMetaData { + + let meta: IMetaData; + + // internally defines recursive function with extra param. This allows cleaner API + const recursiveBuildMeta = ( + // tslint:disable-next-line: no-shadowed-variable + structPropToColumnMap: IDefinition, + isOneOfMany: boolean, + containingColumn: string[] | null, + ownProp: string | null) => { + + const idProps = []; let idColumns = []; - - let propList = keys(structPropToColumnMap); + + const propList = keys(structPropToColumnMap); if (propList.length === 0) { throw new Error('invalid structPropToColumnMap format - property \'' + ownProp + '\' can not be an empty array'); } - - for (let i = 0; i < propList.length; i++) { - let prop = propList[i]; - if ((structPropToColumnMap[prop]).id === true) { - // idProp = prop; + + // Add all of the columns flagged as id to the array + for (const prop of propList) { + if ((structPropToColumnMap[prop] as IDefinitionColumn).id === true) { idProps.push(prop); } } - + + // If no columns are flagged as id, then use the first value in the prop list if (idProps.length === 0) { idProps.push(propList[0]); } - - idColumns = idProps.map(prop => (structPropToColumnMap[prop]).column || structPropToColumnMap[prop]) as Array; - + + idColumns = idProps.map((prop) => { + return (structPropToColumnMap[prop] as IDefinitionColumn).column || structPropToColumnMap[prop]; + }) as string[]; + if (isOneOfMany) { meta.primeIdColumnList.push(idColumns); } - let defaults: Dictionary = {} + const defaults: IDictionary = {}; + + idProps.forEach((prop) => { + defaults[prop] = typeof (structPropToColumnMap[prop] as IDefinitionColumn).default === 'undefined' ? + null : + (structPropToColumnMap[prop] as IDefinitionColumn).default; + }); - idProps.forEach(prop => { - defaults[prop] = typeof (structPropToColumnMap[prop]).default === 'undefined' ? null : (structPropToColumnMap[prop]).default - }) - - let objMeta: MetaColumnData = { + const objMeta: IMetaColumnData = { valueList: [], toOneList: [], arraysList: [], toManyPropList: [], - containingColumn: containingColumn, - ownProp: ownProp, + containingColumn, + ownProp, isOneOfMany: isOneOfMany === true, cache: {}, containingIdUsage: containingColumn === null ? null : {}, - defaults: defaults + defaults, }; - - for (let i = 0; i < propList.length; i++) { - let prop = propList[i]; + + for (const prop of propList) { if (typeof structPropToColumnMap[prop] === 'string') { // value property objMeta.valueList.push({ - prop: prop, + prop, column: structPropToColumnMap[prop] as string, type: undefined, default: undefined }); - } else if ((structPropToColumnMap[prop]).column) { + } else if ((structPropToColumnMap[prop] as IDefinitionColumn).column) { // value property - const definitionColumn = structPropToColumnMap[prop] + const definitionColumn = structPropToColumnMap[prop] as IDefinitionColumn; const metaValueProps = { - prop: prop, + prop, column: definitionColumn.column, type: definitionColumn.type, - default: definitionColumn.default - } - + default: definitionColumn.default, + }; + // Add this column to our array list if necessary if (definitionColumn.array === true) { objMeta.arraysList.push(metaValueProps); @@ -399,173 +488,82 @@ module NestHydrationJS { } else if (isArray(structPropToColumnMap[prop])) { // list of objects / to-many relation objMeta.toManyPropList.push(prop); - - _buildMeta((>structPropToColumnMap[prop])[0], true, idColumns, prop); + + recursiveBuildMeta((structPropToColumnMap[prop] as IDefinition[])[0], true, idColumns, prop); } else if (isPlainObject(structPropToColumnMap[prop])) { // object / to-one relation - + let subIdColumn = values(structPropToColumnMap[prop])[0]; if (typeof subIdColumn === 'undefined') { throw new Error('invalid structPropToColumnMap format - property \'' + prop + '\' can not be an empty object'); } - + if (subIdColumn.column) { subIdColumn = subIdColumn.column; } - + objMeta.toOneList.push({ - prop: prop, - column: subIdColumn + prop, + column: subIdColumn, }); - _buildMeta(structPropToColumnMap[prop], false, idColumns, prop); + recursiveBuildMeta(structPropToColumnMap[prop] as IDefinition, false, idColumns, prop); } else { - throw new Error('invalid structPropToColumnMap format - property \'' + prop + '\' must be either a string, a plain object or an array'); + throw new Error('invalid structPropToColumnMap format - property \'' + prop + + '\' must be either a string, a plain object or an array'); } } - + meta.idMap[createCompositeKey(idColumns)] = objMeta; }; - + // this data structure is populated by the _buildMeta function meta = { primeIdColumnList: [], - idMap: {} - } as MetaData; - + idMap: {}, + } as IMetaData; + if (isArray(structPropToColumnMap)) { if (structPropToColumnMap.length !== 1) { - throw new Error('invalid structPropToColumnMap format - can not have multiple roots for structPropToColumnMap, if an array it must only have one item'); + // tslint:disable-next-line: max-line-length + throw new Error(`invalid structPropToColumnMap format - can not have multiple roots for structPropToColumnMap, if an array it must only have one item`); } // call with first object, but inform _buildMeta it is an array - _buildMeta((>structPropToColumnMap)[0], true, null, null); + recursiveBuildMeta((structPropToColumnMap as IDefinition[])[0], true, null, null); } else if (isPlainObject(structPropToColumnMap)) { // register first column as prime id column - let columns = values(structPropToColumnMap) as any[]; + const columns = values(structPropToColumnMap) as any[]; if (columns.length === 0) { throw new Error('invalid structPropToColumnMap format - the base object can not be an empty object'); } // First determine if there are any keys set on the columns - let keys = columns.reduce((accumulator: string[], column: any) => { + const idColumns = columns.reduce((accumulator: string[], column: any) => { if (column.id === true) { - accumulator.push(column.column) + accumulator.push(column.column); } return accumulator; }, []); - + // If there were no keys set, then take the first column as the id - if (keys.length === 0) { + if (idColumns.length === 0) { if (typeof columns[0] === 'string') { - keys.push(columns[0]) - } else if(typeof columns[0].column === 'string') { - keys.push(columns[0].column) + idColumns.push(columns[0]); + } else if (typeof columns[0].column === 'string') { + idColumns.push(columns[0].column); } } - meta.primeIdColumnList.push(keys); - + meta.primeIdColumnList.push(idColumns); + // construct the rest - _buildMeta(structPropToColumnMap, false, null, null); + recursiveBuildMeta(structPropToColumnMap as IDefinition, false, null, null); } return meta; - }; - - /* Returns a property mapping data structure based on the names of columns - * in columnList. Used internally by nest when its propertyMapping param - * is not specified. - * - */ - structPropToColumnMapFromColumnHints(columnList: Array, renameMapping?: Dictionary) { - - if (typeof renameMapping === 'undefined') { - renameMapping = {}; - } - - let propertyMapping: any = {base: null}; - - for (let i = 0; i < columnList.length; i++) { - let column = columnList[i]; - - let columnType = column.split('___'); - - let type = null; - let isId = false; - let isArray = false; - for (let j = 1; j < columnType.length; j++) { - if (columnType[j] === 'ID') { - isId = true; - } else if (columnType[j] === 'ARRAY') { - isArray = true; - } else if (typeof this.typeHandlers[columnType[j]] !== 'undefined') { - type = columnType[j]; - } - } - - let pointer = propertyMapping; // point to base on each new column - let prop: string | number = 'base'; - - let navList = columnType[0].split('_'); - - for (let j = 0; j < navList.length; j++) { - let nav = navList[j]; - - if (nav === '') { - if (pointer[prop] === null) { - pointer[prop] = [null]; - } - pointer = pointer[prop]; - prop = 0; - } else { - if (pointer[prop] === null) { - pointer[prop] = {}; - } - if (typeof pointer[prop][nav] === 'undefined') { - let renamedColumn: any = typeof renameMapping[column] === 'undefined' - ? column - : renameMapping[column] - ; - if (type !== null || isId) { - // no longer a simple mapping, has need of the type or id properties - renamedColumn = {column: renamedColumn}; - } - if (type !== null) { - // detail the type in the column map if type provided - renamedColumn.type = type; - } - if (isId) { - // set the id property in the column map - renamedColumn.id = true; - } - if (isArray) { - renamedColumn.array = true; - } - pointer[prop][nav] = j === (navList.length - 1) - ? renamedColumn // is leaf node, store full column string - : null // iteration will replace with object or array - ; - } - pointer = pointer[prop]; - prop = nav; - } - } - } - - return propertyMapping.base; - }; - - /* Registers a custom type handler */ - registerType(name: string, handler: TypeHandler) { - if (this.typeHandlers[name]) { - throw new Error('Handler with type, ' + name + ', already exists'); - } - - this.typeHandlers[name] = handler; - }; - } - + } } + } // We have to wrap this in a function for backwards compatablity -export = function generate(): NestHydrationJS.NestHydrationJS { return new NestHydrationJS.NestHydrationJS() }; +export = function generate(): NestHydrationJS.NestHydrationJS { return new NestHydrationJS.NestHydrationJS(); }; From da9ddbd1dd81b3c4119136ff47adb89164a18548 Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Thu, 7 Mar 2019 16:46:48 -0800 Subject: [PATCH 16/25] add build phase to travis.yml --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 9b0f74b..ecd1019 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,8 @@ node_js: - "6" - "8" - "node" +before_script: + - npm run build script: - npm test after_script: From 6428b009683d1d2fe2c1c85acc6808aa73805157 Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Thu, 7 Mar 2019 18:27:29 -0800 Subject: [PATCH 17/25] update change log, add more tests, fix bug with array hint --- CHANGELOG.md | 9 + package.json | 2 +- spec/NestHydrationJS/buildMeta.spec.js | 210 ++++++++++++++++++ spec/NestHydrationJS/nest.spec.js | 121 +++++++--- ...ructPropToColumnMapFromColumnHints.spec.js | 17 ++ src/NestHydrationJS.ts | 7 +- 6 files changed, 336 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10ac1cb..299d3e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [0.4.0] - 2019-03-07 +### Added +- Array support +- Composite keys +- TypeScript definitions + +### Changed +- Converted project to TypeScript + ## [0.3.0] - 2016-06-07 ### Added - Changelog diff --git a/package.json b/package.json index 1076546..d0b8ffa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nesthydrationjs", - "version": "1.1.0", + "version": "1.0.6", "description": "Provides nested objects from tabular data.", "keywords": [ "Hydration", diff --git a/spec/NestHydrationJS/buildMeta.spec.js b/spec/NestHydrationJS/buildMeta.spec.js index faebb75..3deabf0 100644 --- a/spec/NestHydrationJS/buildMeta.spec.js +++ b/spec/NestHydrationJS/buildMeta.spec.js @@ -136,6 +136,39 @@ describe('NestHydrationJS', function () { }); }); + describe('simple mapping with array flag', function () { + var result; + beforeEach(function () { + var mapping = { + a: {column: 'a', array: true} + }; + result = NestHydrationJS.buildMeta(mapping); + }); + + it('should match expected structure', function () { + var expected = { + primeIdColumnList: [['a']], + idMap: { + a: { + valueList: [], + toOneList: [], + toManyPropList: [], + arraysList: [ + {prop: 'a', column: 'a', type: undefined, default: undefined} + ], + containingColumn: null, + ownProp: null, + isOneOfMany: false, + cache: {}, + containingIdUsage: null, + defaults: {a: null} + } + } + }; + expect(result).toEqual(expected); + }); + }); + describe('multiple mapping', function () { var result; beforeEach(function () { @@ -171,6 +204,77 @@ describe('NestHydrationJS', function () { }); }); + describe('multiple mapping with single array flag', function () { + var result; + beforeEach(function () { + var mapping = { + a: {column: 'a', array: true}, + b: 'b' + }; + result = NestHydrationJS.buildMeta(mapping); + }); + + it('should match expected structure', function () { + var expected = { + primeIdColumnList: [['a']], + idMap: { + a: { + valueList: [ + {prop: 'b', column: 'b', type: undefined, default: undefined} + ], + toOneList: [], + toManyPropList: [], + arraysList: [ + {prop: 'a', column: 'a', type: undefined, default: undefined} + ], + containingColumn: null, + ownProp: null, + isOneOfMany: false, + cache: {}, + containingIdUsage: null, + defaults: {a: null} + } + } + }; + expect(result).toEqual(expected); + }); + }); + + describe('multiple mapping with both array flags', function () { + var result; + beforeEach(function () { + var mapping = { + a: {column: 'a', array: true}, + b: {column: 'b', array: true} + }; + result = NestHydrationJS.buildMeta(mapping); + }); + + it('should match expected structure', function () { + var expected = { + primeIdColumnList: [['a']], + idMap: { + a: { + valueList: [], + toOneList: [], + toManyPropList: [], + arraysList: [ + {prop: 'a', column: 'a', type: undefined, default: undefined}, + {prop: 'b', column: 'b', type: undefined, default: undefined} + ], + containingColumn: null, + ownProp: null, + isOneOfMany: false, + cache: {}, + containingIdUsage: null, + defaults: {a: null} + } + } + }; + expect(result).toEqual(expected); + }); + }); + describe('multiple mapping having id with default', function () { var result; beforeEach(function () { @@ -258,6 +362,112 @@ describe('NestHydrationJS', function () { }); }); + describe('multiple mapping complex with array flag', function () { + var result; + beforeEach(function () { + var mapping = { + id: 'id', + a: { + c: {column: 'c', array: true} + }, + b: 'b' + }; + result = NestHydrationJS.buildMeta(mapping); + }); + + it('should match expected structure', function () { + var expected = { + primeIdColumnList: [['id']], + idMap: { + c: { + valueList: [], + toOneList: [], + toManyPropList: [], + arraysList: [ + {prop: 'c', column: 'c', type: undefined, default: undefined} + ], + containingColumn: ['id'], + ownProp: 'a', + isOneOfMany: false, + cache: {}, + containingIdUsage: {}, + defaults: {c: null} + }, + id: { + valueList: [ + {prop: 'id', column: 'id', type: undefined, default: undefined}, + {prop: 'b', column: 'b', type: undefined, default: undefined} + ], + toOneList: [{prop: 'a', column: 'c'}], + toManyPropList: [], + arraysList: [], + containingColumn: null, + ownProp: null, + isOneOfMany: false, + cache: {}, + containingIdUsage: null, + defaults: { id: null } + } + } + }; + expect(result).toEqual(expected); + }); + }); + + describe('multiple mapping complex with composite id', function () { + var result; + beforeEach(function () { + var mapping = { + id: 'id', + a: { + c: {column: 'c', id: true}, + d: {column: 'd', id: true} + }, + b: 'b' + }; + result = NestHydrationJS.buildMeta(mapping); + }); + + it('should match expected structure', function () { + var expected = { + primeIdColumnList: [['id']], + idMap: { + 'c, d': { + valueList: [ + { prop: 'c', column: 'c', type: undefined, default: undefined }, + { prop: 'd', column: 'd', type: undefined, default: undefined } + ], + toOneList: [], + toManyPropList: [], + arraysList: [], + containingColumn: ['id'], + ownProp: 'a', + isOneOfMany: false, + cache: {}, + containingIdUsage: {}, + defaults: {c: null, d: null} + }, + id: { + valueList: [ + {prop: 'id', column: 'id', type: undefined, default: undefined}, + {prop: 'b', column: 'b', type: undefined, default: undefined} + ], + toOneList: [{prop: 'a', column: 'c'}], + toManyPropList: [], + arraysList: [], + containingColumn: null, + ownProp: null, + isOneOfMany: false, + cache: {}, + containingIdUsage: null, + defaults: { id: null } + } + } + }; + expect(result).toEqual(expected); + }); + }); + describe('multiple mapping array', function () { var result; beforeEach(function () { diff --git a/spec/NestHydrationJS/nest.spec.js b/spec/NestHydrationJS/nest.spec.js index ce47a4a..5f49185 100644 --- a/spec/NestHydrationJS/nest.spec.js +++ b/spec/NestHydrationJS/nest.spec.js @@ -256,6 +256,22 @@ describe('NestHydrationJS', function () { }); }); + describe('simple mapping, array', function() { + var result; + beforeEach(function () { + var mapping = { + arr: { column: 'arr', array: true } + }; + var data = {arr: 'arr1'}; + result = NestHydrationJS.nest(data, mapping); + }); + + it('should match expected structure', function () { + var expected = {arr: ['arr1'] }; + expect(result).toEqual(expected); + }); + }); + describe('multiple mapping', function () { var result; beforeEach(function () { @@ -306,6 +322,76 @@ describe('NestHydrationJS', function () { }); }); + describe('multiple mapping, multiple arrays', function() { + var result; + beforeEach(function () { + var mapping = { + a: 'a', + arr: { column: 'arr', array: true }, + arr2: { column: 'arr2', array :true } + }; + var data = {a: 'value 1', arr: 'arr1', arr2: 'arr2'}; + result = NestHydrationJS.nest(data, mapping); + }); + + it('should match expected structure', function () { + var expected = {a: 'value 1', arr: ['arr1'], arr2: ['arr2'] }; + expect(result).toEqual(expected); + }); + }); + + describe('multiple mapping array, with array', function() { + var result; + beforeEach(function () { + var mapping = [{ + a: 'a', + arr: { column: 'arr', array: true }, + }]; + var data = [ + {a: 'a1', arr: 'arr1'}, + {a: 'a1', arr: 'arr1-2'}, + {a: 'a2', arr: 'arr2'}, + {a: 'a2', arr: 'arr2-2'}, + {a: 'a3', arr: 'arr3'}, + {a: 'a3', arr: 'arr3-3'} + ]; + result = NestHydrationJS.nest(data, mapping); + }); + + it('should match expected structure', function () { + var expected = [ { a: 'a1', arr: [ 'arr1', 'arr1-2' ] }, + { a: 'a2', arr: [ 'arr2', 'arr2-2' ] }, + { a: 'a3', arr: [ 'arr3', 'arr3-3' ] } ]; + expect(result).toEqual(expected); + }); + }); + + describe('multiple mapping array with composite id, with array', function() { + var result; + beforeEach(function () { + var mapping = [{ + a: { column: 'a', id: true }, + b: { column: 'b', id: true }, + arr: { column: 'arr', array: true }, + }]; + var data = [ + {a: 'a1', b: 'b1', arr: 'arr1'}, + {a: 'a1', b: 'b1', arr: 'arr1-2'}, + {a: 'a2', b: 'b1', arr: 'arr2'}, + {a: 'a2', b: 'b1', arr: 'arr2-2'} + ]; + result = NestHydrationJS.nest(data, mapping); + }); + + it('should match expected structure', function () { + var expected = [ + { a: 'a1', b: 'b1', arr: [ 'arr1', 'arr1-2' ] }, + { a: 'a2', b: 'b1', arr: [ 'arr2', 'arr2-2' ] } + ]; + expect(result).toEqual(expected); + }); + }); + describe('multiple mapping array', function () { var result; beforeEach(function () { @@ -389,26 +475,26 @@ describe('NestHydrationJS', function () { b: {column: 'b', id: true} }]; var data = [ - {a: 'value a1', b: 'value b1'}, - {a: 'value a1', b: 'value b2'}, - {a: 'value a2', b: 'value b1'}, - {a: 'value a2', b: 'value b2'} + {a: 'a1', b: 'b1'}, + {a: 'a1', b: 'b2'}, + {a: 'a2', b: 'b1'}, + {a: 'a2', b: 'b2'} ]; result = NestHydrationJS.nest(data, mapping); }); it('should match expected structure', function () { var expected = [ - {a: 'value a1', b: 'value b1'}, - {a: 'value a1', b: 'value b2'}, - {a: 'value a2', b: 'value b1'}, - {a: 'value a2', b: 'value b2'} + {a: 'a1', b: 'b1'}, + {a: 'a1', b: 'b2'}, + {a: 'a2', b: 'b1'}, + {a: 'a2', b: 'b2'} ]; expect(result).toEqual(expected); }); }); - describe('multiple mapping array with composite id and multiple mapping', function () { + describe('multiple mapping array with composite id and to-many relation', function () { var result; beforeEach(function () { var mapping = [{ @@ -436,23 +522,6 @@ describe('NestHydrationJS', function () { }); }); - describe('multiple mapping array with composite id and multiple mapping', function () { - var result; - beforeEach(function () { - var mapping = { - a: {column: 'a', id: true}, - b: {column: 'b', id: true}, - }; - var data = {a: 'a1', b: 'b1'}; - result = NestHydrationJS.nest(data, mapping, true); - }); - - it('should match expected structure', function () { - var expected = {a: 'a1', b: 'b1'}; - expect(result).toEqual(expected); - }); - }); - describe('multiple mapping array, hinted mapping', function () { var result; beforeEach(function () { diff --git a/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js b/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js index e474182..2c1e010 100644 --- a/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js +++ b/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js @@ -49,6 +49,23 @@ describe('NestHydrationJS', function () { expect(result).toEqual(expected); }); }); + + describe('passed single direct property as columnList, array flag', function () { + var result; + beforeEach(function () { + var columnList = [ + 'a___ARRAY' + ]; + result = NestHydrationJS.structPropToColumnMapFromColumnHints(columnList); + }); + + it('should match expected structure', function () { + var expected = { + a: {column: 'a___ARRAY', array: true} + }; + expect(result).toEqual(expected); + }); + }); describe('passed single direct property as columnList, boolean type', function () { var result; diff --git a/src/NestHydrationJS.ts b/src/NestHydrationJS.ts index 814f91c..14a6aee 100644 --- a/src/NestHydrationJS.ts +++ b/src/NestHydrationJS.ts @@ -311,11 +311,12 @@ namespace NestHydrationJS { for (let j = 1; j < columnType.length; j++) { if (columnType[j] === 'ID') { idFlagSet = true; - } else if (columnType[j] === 'ARRAY') { - arrayFlagSet = true; } else if (typeof this.typeHandlers[columnType[j]] !== 'undefined') { type = columnType[j]; } + if (columnType[j] === 'ARRAY') { + arrayFlagSet = true; + } } let pointer = propertyMapping; // point to base on each new column @@ -341,7 +342,7 @@ namespace NestHydrationJS { ? column : renameMapping[column] ; - if (type !== null || idFlagSet) { + if (type !== null || idFlagSet || arrayFlagSet) { // no longer a simple mapping, has need of the type or id properties renamedColumn = {column: renamedColumn}; } From 5c97fab7a0cf98de790146f4d846c37ae173c155 Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Thu, 7 Mar 2019 18:31:48 -0800 Subject: [PATCH 18/25] Update docs to note ___ARRAY hinting and add more hinting tests --- README.md | 2 +- ...ructPropToColumnMapFromColumnHints.spec.js | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 28bfe32..c914662 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ The following example gives same result as above but a column naming convention Nesting is achieved by using a underscore (`_`). A *x to one* relation is defined by a single underscore and a *x to many* relation is defined by preceeding properties of the many object with a 2nd underscore. -If a column alias ends in a triple underscore (`___`) followed by either `NUMBER` or `BOOLEAN` then the values in those columns will be caste to the respective type unless the value is null. Triple underscore with `ID` (`___ID`) can be used to specify a column that is an id propery of that level of object. If an id is not specified the default is for the first column in that object to be the id property. The id specifier can be used in combination with a type caste, so either `___ID___NUMBER`, or `___NUMBER___ID` would be valid appends to a column name. +If a column alias ends in a triple underscore (`___`) followed by either `NUMBER` or `BOOLEAN` then the values in those columns will be caste to the respective type unless the value is null. Triple underscore with `ID` (`___ID`) can be used to specify a column that is an id propery of that level of object. Triple underscore with `ARRAY` (`___ARRAY`) can be used to specify a column that should be treated as an array. If an id is not specified the default is for the first column in that object to be the id property. The id specifier can be used in combination with a type caste, so either `___ID___NUMBER`, or `___NUMBER___ID` would be valid appends to a column name. **Note:** that this means that almost always base level properties will be prefixed with a underscore, as this is actually a *x to many* relation from the variable returned from the `nest` function. diff --git a/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js b/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js index 2c1e010..0f1cee2 100644 --- a/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js +++ b/spec/NestHydrationJS/structPropToColumnMapFromColumnHints.spec.js @@ -120,7 +120,7 @@ describe('NestHydrationJS', function () { }); }); - describe('passed single direct property as columnList, id column and typed', function () { + describe('passed single direct property as columnList, id column and type', function () { var result; beforeEach(function () { var columnList = [ @@ -136,6 +136,23 @@ describe('NestHydrationJS', function () { expect(result).toEqual(expected); }); }); + + describe('passed single direct property as columnList, id column, type and array', function () { + var result; + beforeEach(function () { + var columnList = [ + 'a___ID___NUMBER___ARRAY' + ]; + result = NestHydrationJS.structPropToColumnMapFromColumnHints(columnList); + }); + + it('should match expected structure', function () { + var expected = { + a: {column: 'a___ID___NUMBER___ARRAY', type: 'NUMBER', id: true, array: true} + }; + expect(result).toEqual(expected); + }); + }); describe('passed single direct property as columnList, id column and typed, reverse order', function () { var result; From d2db40057f2064a620533ccdd1c2df22ed318a17 Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Thu, 7 Mar 2019 19:13:50 -0800 Subject: [PATCH 19/25] replace Array#includes with Array#indexOf for older Node support --- src/NestHydrationJS.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NestHydrationJS.ts b/src/NestHydrationJS.ts index 14a6aee..36d1b9e 100644 --- a/src/NestHydrationJS.ts +++ b/src/NestHydrationJS.ts @@ -168,7 +168,7 @@ namespace NestHydrationJS { return value; }); - if (vals.includes(null)) { return; } + if (vals.indexOf(null) !== -1) { return; } // check if object already exists in cache if (typeof objMeta.cache[createCompositeKey(vals)] !== 'undefined') { From 9c2a42dfa17939eabee473662916384a1f42dffe Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Fri, 8 Mar 2019 10:23:50 -0800 Subject: [PATCH 20/25] remove node 0.x as it's not supported by jasmine anymore --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ecd1019..cf00cd4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ branches: - master - qc node_js: - - "0.12.5" - "4" - "6" - "8" From c8c35e689b0e38a8d9a7fbd4b0198cfcf89f89db Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Sat, 16 Mar 2019 19:23:10 -0700 Subject: [PATCH 21/25] fix typescript errors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d0b8ffa..eea5def 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ ], "license": "ISC", "main": "lib/NestHydrationJS.js", - "types": "lib/NestHydrationJS.d.js", + "types": "lib/NestHydrationJS.d.ts", "scripts": { "coverage": "nyc jasmine", "coveralls": "nyc report --reporter=text-lcov | coveralls", From d95b4f62898457eb02efd6ee97e10a207656dd58 Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Fri, 3 May 2019 11:59:53 -0700 Subject: [PATCH 22/25] fix bug where to-one relations would not have the proper keys --- package-lock.json | 2 +- src/NestHydrationJS.ts | 28 ++++++++++++++++------------ tsconfig.json | 3 ++- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6d9b110..305ff5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "nesthydrationjs", - "version": "1.1.0", + "version": "1.0.6", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/src/NestHydrationJS.ts b/src/NestHydrationJS.ts index 36d1b9e..afab921 100644 --- a/src/NestHydrationJS.ts +++ b/src/NestHydrationJS.ts @@ -17,7 +17,7 @@ namespace NestHydrationJS { interface IMetaValueProps { prop: string; - column: string; + column: string | string[]; type?: string | ITypeHandler; default?: any; } @@ -178,7 +178,7 @@ namespace NestHydrationJS { // Add array values if necessary for (const prop of objMeta.arraysList) { - const cellValue = this.computeActualCellValue(prop, row[prop.column]); + const cellValue = this.computeActualCellValue(prop, row[prop.column as string]); if (isArray(obj[prop.prop])) { obj[prop.prop].push(cellValue); } else { @@ -205,13 +205,13 @@ namespace NestHydrationJS { // copy in properties from table data for (const prop of objMeta.valueList) { - const cellValue = this.computeActualCellValue(prop, row[prop.column]); + const cellValue = this.computeActualCellValue(prop, row[prop.column as string]); obj[prop.prop] = cellValue; } // Add array values for (const prop of objMeta.arraysList) { - const cellValue = this.computeActualCellValue(prop, row[prop.column]); + const cellValue = this.computeActualCellValue(prop, row[prop.column as string]); if (isArray(obj[prop.prop])) { obj[prop.prop].push(cellValue); } else { @@ -228,7 +228,7 @@ namespace NestHydrationJS { // initialize null to-one relations and then recursively build them for (const prop of objMeta.toOneList) { obj[prop.prop] = null; - recursiveNest(row, [prop.column]); + recursiveNest(row, Array.isArray(prop.column) ? prop.column : [prop.column]); } } @@ -494,18 +494,22 @@ namespace NestHydrationJS { } else if (isPlainObject(structPropToColumnMap[prop])) { // object / to-one relation - let subIdColumn = values(structPropToColumnMap[prop])[0]; - if (typeof subIdColumn === 'undefined') { - throw new Error('invalid structPropToColumnMap format - property \'' + prop + '\' can not be an empty object'); - } + const subIdProps = []; - if (subIdColumn.column) { - subIdColumn = subIdColumn.column; + for (const value of values(structPropToColumnMap[prop])) { + if (typeof value === 'object' && value.id === true) { + subIdProps.push(value.column) + } } + // If no columns are flagged as id, then use the first value in the prop list + if (subIdProps.length === 0) { + subIdProps.push(values(structPropToColumnMap[prop])[0]); + } + objMeta.toOneList.push({ prop, - column: subIdColumn, + column: subIdProps, }); recursiveBuildMeta(structPropToColumnMap[prop] as IDefinition, false, idColumns, prop); } else { diff --git a/tsconfig.json b/tsconfig.json index 764f7f7..037ef80 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,8 @@ "module": "commonjs", "declaration": true, "outDir": "./lib", - "strict": true + "strict": true, + "sourceMap": true }, "include": ["src/**/*", "test.ts"], "exclude": ["node_modules/", "spec"] From ee6fbfe2a0826ada1b43b72d3c71e485214ec8d5 Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Sun, 12 May 2019 15:38:31 -0700 Subject: [PATCH 23/25] fix bug where ID columns would sometimes not be identified --- src/NestHydrationJS.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/NestHydrationJS.ts b/src/NestHydrationJS.ts index afab921..c1963f5 100644 --- a/src/NestHydrationJS.ts +++ b/src/NestHydrationJS.ts @@ -504,7 +504,8 @@ namespace NestHydrationJS { // If no columns are flagged as id, then use the first value in the prop list if (subIdProps.length === 0) { - subIdProps.push(values(structPropToColumnMap[prop])[0]); + const column = values(structPropToColumnMap[prop])[0]; + subIdProps.push(typeof column === 'object' ? column.column : column); } objMeta.toOneList.push({ From a61d56116483b665a9856053fe502dc96370798c Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Sun, 12 May 2019 15:42:37 -0700 Subject: [PATCH 24/25] add lib to git --- .gitignore | 4 +- lib/NestHydrationJS.d.ts | 27 +++ lib/NestHydrationJS.js | 480 +++++++++++++++++++++++++++++++++++++ lib/NestHydrationJS.js.map | 1 + 4 files changed, 511 insertions(+), 1 deletion(-) create mode 100644 lib/NestHydrationJS.d.ts create mode 100644 lib/NestHydrationJS.js create mode 100644 lib/NestHydrationJS.js.map diff --git a/.gitignore b/.gitignore index e22b266..a033cca 100644 --- a/.gitignore +++ b/.gitignore @@ -13,5 +13,7 @@ !/package.json !/README.md !/src/** +!/lib/** +!/lib !/tsconfig.json -!/tslint.json \ No newline at end of file +!/tslint.json diff --git a/lib/NestHydrationJS.d.ts b/lib/NestHydrationJS.d.ts new file mode 100644 index 0000000..85eb29d --- /dev/null +++ b/lib/NestHydrationJS.d.ts @@ -0,0 +1,27 @@ +declare namespace NestHydrationJS { + type ITypeHandler = (cellValue: any) => any; + interface IDictionary { + [index: string]: TValue; + } + interface IDefinitionColumn { + column: string; + id?: boolean; + default?: any; + type?: string; + array?: boolean; + } + interface IDefinition { + [index: string]: IDefinitionColumn | string | IDefinition | IDefinition[]; + } + class NestHydrationJS { + private typeHandlers; + private struct; + nest(data: any, structPropToColumnMap: IDefinition | IDefinition[] | null | boolean, verbose?: boolean): any; + structPropToColumnMapFromColumnHints(columnList: string[], renameMapping?: IDictionary): any; + registerType(name: string, handler: ITypeHandler): void; + private computeActualCellValue; + private buildMeta; + } +} +declare const _default: () => NestHydrationJS.NestHydrationJS; +export = _default; diff --git a/lib/NestHydrationJS.js b/lib/NestHydrationJS.js new file mode 100644 index 0000000..06a5494 --- /dev/null +++ b/lib/NestHydrationJS.js @@ -0,0 +1,480 @@ +'use strict'; +var isArray = require("lodash.isarray"); +var isFunction = require("lodash.isfunction"); +var isPlainObject = require("lodash.isplainobject"); +var keys = require("lodash.keys"); +var values = require("lodash.values"); +// tslint:disable-next-line: no-namespace +var NestHydrationJS; +(function (NestHydrationJS_1) { + function createCompositeKey(vals, separator) { + if (separator === void 0) { separator = ', '; } + return vals.join(separator); + } + // tslint:disable-next-line: no-shadowed-variable + var NestHydrationJS = /** @class */ (function () { + function NestHydrationJS() { + this.typeHandlers = { + NUMBER: function (cellValue) { + return parseFloat(cellValue); + }, + BOOLEAN: function (cellValue) { + // tslint:disable-next-line: triple-equals + return cellValue == true; + }, + }; + this.struct = null; + } + /* Creates a data structure containing nested objects and/or arrays from + * tabular data based on a structure definition provided by + * structPropToColumnMap. If structPropToColumnMap is not provided but + * the data has column names that follow a particular convention then a + * nested structures can also be created. + */ + NestHydrationJS.prototype.nest = function (data, structPropToColumnMap, verbose) { + var _this = this; + if (verbose === void 0) { verbose = false; } + var table; + // VALIDATE PARAMS AND BASIC INITIALIZATION + // Determines that on no results, and empty list is used instead of null. // NOTE: fact check this + var listOnEmpty = false; + if (typeof structPropToColumnMap === 'undefined') { + structPropToColumnMap = null; + } + if (data === null) { + return null; + } + if (!isArray(structPropToColumnMap) && !isPlainObject(structPropToColumnMap) && + structPropToColumnMap !== null && structPropToColumnMap !== true) { + throw new Error('nest expects param structPropToColumnMap to be an array, plain object, null, or true'); + } + if (isPlainObject(data)) { + // internal table should be a table format but a plain object + // could be passed as the first (and only) row of that table + table = [data]; + } + else if (isArray(data)) { + table = data; + } + else { + // tslint:disable-next-line: max-line-length + throw Error("nest expects param data to be in the form of a plain object or an array of plain objects (forming a table)"); + } + // structPropToColumnMap can be set to true as a tie break between + // returning null (empty structure) or an empty list + if (structPropToColumnMap === true) { + listOnEmpty = true; + structPropToColumnMap = null; + } + if (structPropToColumnMap === null && table.length > 0) { + // property mapping not specified, determine it from column names + structPropToColumnMap = this.structPropToColumnMapFromColumnHints(keys(table[0])); + } + if (structPropToColumnMap === null) { + // properties is empty, can't form structure or determine content + // for a list. Assume a structure unless listOnEmpty + return listOnEmpty ? [] : null; + } + else if (table.length === 0) { + // table is empty, return the appropriate empty result based on input definition + return isArray(structPropToColumnMap) ? [] : null; + } + // COMPLETE VALIDATING PARAMS AND BASIC INITIALIZATION + var meta = this.buildMeta(structPropToColumnMap); + // BUILD FROM TABLE + // defines function that can be called recursively + var recursiveNest = function (row, idColumns) { + // Obj is the actual object that will end up in the final structure + var obj; + // Get all of the values for each id + var vals = idColumns.map(function (column) { return row[column]; }); + // only really concerned with the meta data for this identity column + var objMeta = meta.idMap[createCompositeKey(idColumns)]; + // If any of the values are null, we'll check and see if we need to set defaults + vals = vals.map(function (value, idx) { + if (value === null) { + if (objMeta.defaults[idColumns[idx]] !== null && typeof objMeta.defaults[idColumns[idx]] !== 'undefined') { + return objMeta.defaults[idColumns[idx]]; + } + } + return value; + }); + if (vals.indexOf(null) !== -1) { + return; + } + // check if object already exists in cache + if (typeof objMeta.cache[createCompositeKey(vals)] !== 'undefined') { + // not already placed as to-many relation in container + obj = objMeta.cache[createCompositeKey(vals)]; + // Add array values if necessary + for (var _i = 0, _a = objMeta.arraysList; _i < _a.length; _i++) { + var prop = _a[_i]; + var cellValue = _this.computeActualCellValue(prop, row[prop.column]); + if (isArray(obj[prop.prop])) { + obj[prop.prop].push(cellValue); + } + else { + obj[prop.prop] = [cellValue]; + } + } + if (objMeta.containingIdUsage === null) { + return; + } + // We know for certain that containing column is set if + // containingIdUsage is not null and can cast it as a string + // check and see if this has already been linked to the parent, + // and if so we don't need to continue + var containingIds = objMeta.containingColumn.map(function (column) { return row[column]; }); + if (typeof objMeta.containingIdUsage[createCompositeKey(vals)] !== 'undefined' + && typeof objMeta.containingIdUsage[createCompositeKey(vals)][createCompositeKey(containingIds)] !== 'undefined') { + return; + } + } + else { + // don't have an object defined for this yet, create it and set the cache + obj = {}; + objMeta.cache[createCompositeKey(vals)] = obj; + // copy in properties from table data + for (var _b = 0, _c = objMeta.valueList; _b < _c.length; _b++) { + var prop = _c[_b]; + var cellValue = _this.computeActualCellValue(prop, row[prop.column]); + obj[prop.prop] = cellValue; + } + // Add array values + for (var _d = 0, _e = objMeta.arraysList; _d < _e.length; _d++) { + var prop = _e[_d]; + var cellValue = _this.computeActualCellValue(prop, row[prop.column]); + if (isArray(obj[prop.prop])) { + obj[prop.prop].push(cellValue); + } + else { + obj[prop.prop] = [cellValue]; + } + } + // initialize empty to-many relations, they will be populated when + // those objects build themselves and find this containing object + for (var _f = 0, _g = objMeta.toManyPropList; _f < _g.length; _f++) { + var prop = _g[_f]; + obj[prop] = []; + } + // initialize null to-one relations and then recursively build them + for (var _h = 0, _j = objMeta.toOneList; _h < _j.length; _h++) { + var prop = _j[_h]; + obj[prop.prop] = null; + recursiveNest(row, Array.isArray(prop.column) ? prop.column : [prop.column]); + } + } + // link from the parent + if (objMeta.containingColumn === null) { + // parent is the top level + if (objMeta.isOneOfMany) { + // it is an array + if (_this.struct === null) { + _this.struct = []; + } + _this.struct.push(obj); + } + else { + // it is this object + _this.struct = obj; + } + } + else { + var containingIds = objMeta.containingColumn.map(function (column) { return row[column]; }); + var container = meta.idMap[createCompositeKey(objMeta.containingColumn)] + .cache[createCompositeKey(containingIds)]; + // If a container exists, it must not be a root, and thus there should + // be an ownProp set + if (container) { + if (objMeta.isOneOfMany) { + // it is an array + container[objMeta.ownProp].push(obj); + } + else { + // it is this object + container[objMeta.ownProp] = obj; + } + } + // record the containing id so we don't do this again (return in earlier + // part of this method) + var containingIdUsage = objMeta.containingIdUsage; + if (typeof (containingIdUsage)[createCompositeKey(vals)] === 'undefined') { + containingIdUsage[createCompositeKey(vals)] = {}; + } + containingIdUsage[createCompositeKey(vals)][createCompositeKey(containingIds)] = true; + } + }; + // struct is populated inside the build function + this.struct = null; + // tslint:disable-next-line: no-console + if (verbose) { + console.log(meta); + } + for (var _i = 0, table_1 = table; _i < table_1.length; _i++) { + var row = table_1[_i]; + for (var _a = 0, _b = meta.primeIdColumnList; _a < _b.length; _a++) { + var primeIdColumn = _b[_a]; + // for each prime id column (corresponding to a to-many relation or + // the top level) attempted to build an object + recursiveNest(row, primeIdColumn); + } + } + return this.struct; + }; + /* Returns a property mapping data structure based on the names of columns + * in columnList. Used internally by nest when its propertyMapping param + * is not specified. + * + */ + NestHydrationJS.prototype.structPropToColumnMapFromColumnHints = function (columnList, renameMapping) { + if (typeof renameMapping === 'undefined') { + renameMapping = {}; + } + var propertyMapping = { base: null }; + for (var _i = 0, columnList_1 = columnList; _i < columnList_1.length; _i++) { + var column = columnList_1[_i]; + var columnType = column.split('___'); + var type = null; + var idFlagSet = false; + var arrayFlagSet = false; + for (var j = 1; j < columnType.length; j++) { + if (columnType[j] === 'ID') { + idFlagSet = true; + } + else if (typeof this.typeHandlers[columnType[j]] !== 'undefined') { + type = columnType[j]; + } + if (columnType[j] === 'ARRAY') { + arrayFlagSet = true; + } + } + var pointer = propertyMapping; // point to base on each new column + var prop = 'base'; + var navList = columnType[0].split('_'); + for (var j = 0; j < navList.length; j++) { + var nav = navList[j]; + if (nav === '') { + if (pointer[prop] === null) { + pointer[prop] = [null]; + } + pointer = pointer[prop]; + prop = 0; + } + else { + if (pointer[prop] === null) { + pointer[prop] = {}; + } + if (typeof pointer[prop][nav] === 'undefined') { + var renamedColumn = typeof renameMapping[column] === 'undefined' + ? column + : renameMapping[column]; + if (type !== null || idFlagSet || arrayFlagSet) { + // no longer a simple mapping, has need of the type or id properties + renamedColumn = { column: renamedColumn }; + } + if (type !== null) { + // detail the type in the column map if type provided + renamedColumn.type = type; + } + if (idFlagSet) { + // set the id property in the column map + renamedColumn.id = true; + } + if (arrayFlagSet) { + renamedColumn.array = true; + } + pointer[prop][nav] = j === (navList.length - 1) + ? renamedColumn // is leaf node, store full column string + : null // iteration will replace with object or array + ; + } + pointer = pointer[prop]; + prop = nav; + } + } + } + return propertyMapping.base; + }; + /* Registers a custom type handler */ + NestHydrationJS.prototype.registerType = function (name, handler) { + if (this.typeHandlers[name]) { + throw new Error('Handler with type, ' + name + ', already exists'); + } + this.typeHandlers[name] = handler; + }; + NestHydrationJS.prototype.computeActualCellValue = function (props, initialValue) { + var cellValue = initialValue; + if (cellValue !== null) { + var valueTypeFunction = void 0; + if (isFunction(props.type)) { + valueTypeFunction = props.type; + } + else if (typeof props.type === 'string') { + valueTypeFunction = this.typeHandlers[props.type]; + } + if (valueTypeFunction) { + cellValue = valueTypeFunction(cellValue); + } + } + else if (typeof props.default !== 'undefined') { + cellValue = props.default; + } + return cellValue; + }; + /* Create a data structure that contains lookups and cache spaces for quick + * reference and action for the workings of the nest method. + */ + NestHydrationJS.prototype.buildMeta = function (structPropToColumnMap) { + var meta; + // internally defines recursive function with extra param. This allows cleaner API + var recursiveBuildMeta = function ( + // tslint:disable-next-line: no-shadowed-variable + structPropToColumnMap, isOneOfMany, containingColumn, ownProp) { + var idProps = []; + var idColumns = []; + var propList = keys(structPropToColumnMap); + if (propList.length === 0) { + throw new Error('invalid structPropToColumnMap format - property \'' + ownProp + '\' can not be an empty array'); + } + // Add all of the columns flagged as id to the array + for (var _i = 0, propList_1 = propList; _i < propList_1.length; _i++) { + var prop = propList_1[_i]; + if (structPropToColumnMap[prop].id === true) { + idProps.push(prop); + } + } + // If no columns are flagged as id, then use the first value in the prop list + if (idProps.length === 0) { + idProps.push(propList[0]); + } + idColumns = idProps.map(function (prop) { + return structPropToColumnMap[prop].column || structPropToColumnMap[prop]; + }); + if (isOneOfMany) { + meta.primeIdColumnList.push(idColumns); + } + var defaults = {}; + idProps.forEach(function (prop) { + defaults[prop] = typeof structPropToColumnMap[prop].default === 'undefined' ? + null : + structPropToColumnMap[prop].default; + }); + var objMeta = { + valueList: [], + toOneList: [], + arraysList: [], + toManyPropList: [], + containingColumn: containingColumn, + ownProp: ownProp, + isOneOfMany: isOneOfMany === true, + cache: {}, + containingIdUsage: containingColumn === null ? null : {}, + defaults: defaults, + }; + for (var _a = 0, propList_2 = propList; _a < propList_2.length; _a++) { + var prop = propList_2[_a]; + if (typeof structPropToColumnMap[prop] === 'string') { + // value property + objMeta.valueList.push({ + prop: prop, + column: structPropToColumnMap[prop], + type: undefined, + default: undefined + }); + } + else if (structPropToColumnMap[prop].column) { + // value property + var definitionColumn = structPropToColumnMap[prop]; + var metaValueProps = { + prop: prop, + column: definitionColumn.column, + type: definitionColumn.type, + default: definitionColumn.default, + }; + // Add this column to our array list if necessary + if (definitionColumn.array === true) { + objMeta.arraysList.push(metaValueProps); + } + else { + objMeta.valueList.push(metaValueProps); + } + } + else if (isArray(structPropToColumnMap[prop])) { + // list of objects / to-many relation + objMeta.toManyPropList.push(prop); + recursiveBuildMeta(structPropToColumnMap[prop][0], true, idColumns, prop); + } + else if (isPlainObject(structPropToColumnMap[prop])) { + // object / to-one relation + var subIdProps = []; + for (var _b = 0, _c = values(structPropToColumnMap[prop]); _b < _c.length; _b++) { + var value = _c[_b]; + if (typeof value === 'object' && value.id === true) { + subIdProps.push(value.column); + } + } + // If no columns are flagged as id, then use the first value in the prop list + if (subIdProps.length === 0) { + var column = values(structPropToColumnMap[prop])[0]; + subIdProps.push(typeof column === 'object' ? column.column : column); + } + objMeta.toOneList.push({ + prop: prop, + column: subIdProps, + }); + recursiveBuildMeta(structPropToColumnMap[prop], false, idColumns, prop); + } + else { + throw new Error('invalid structPropToColumnMap format - property \'' + prop + + '\' must be either a string, a plain object or an array'); + } + } + meta.idMap[createCompositeKey(idColumns)] = objMeta; + }; + // this data structure is populated by the _buildMeta function + meta = { + primeIdColumnList: [], + idMap: {}, + }; + if (isArray(structPropToColumnMap)) { + if (structPropToColumnMap.length !== 1) { + // tslint:disable-next-line: max-line-length + throw new Error("invalid structPropToColumnMap format - can not have multiple roots for structPropToColumnMap, if an array it must only have one item"); + } + // call with first object, but inform _buildMeta it is an array + recursiveBuildMeta(structPropToColumnMap[0], true, null, null); + } + else if (isPlainObject(structPropToColumnMap)) { + // register first column as prime id column + var columns = values(structPropToColumnMap); + if (columns.length === 0) { + throw new Error('invalid structPropToColumnMap format - the base object can not be an empty object'); + } + // First determine if there are any keys set on the columns + var idColumns = columns.reduce(function (accumulator, column) { + if (column.id === true) { + accumulator.push(column.column); + } + return accumulator; + }, []); + // If there were no keys set, then take the first column as the id + if (idColumns.length === 0) { + if (typeof columns[0] === 'string') { + idColumns.push(columns[0]); + } + else if (typeof columns[0].column === 'string') { + idColumns.push(columns[0].column); + } + } + meta.primeIdColumnList.push(idColumns); + // construct the rest + recursiveBuildMeta(structPropToColumnMap, false, null, null); + } + return meta; + }; + return NestHydrationJS; + }()); + NestHydrationJS_1.NestHydrationJS = NestHydrationJS; +})(NestHydrationJS || (NestHydrationJS = {})); +module.exports = function generate() { return new NestHydrationJS.NestHydrationJS(); }; +//# sourceMappingURL=NestHydrationJS.js.map \ No newline at end of file diff --git a/lib/NestHydrationJS.js.map b/lib/NestHydrationJS.js.map new file mode 100644 index 0000000..da1493a --- /dev/null +++ b/lib/NestHydrationJS.js.map @@ -0,0 +1 @@ +{"version":3,"file":"NestHydrationJS.js","sourceRoot":"","sources":["../src/NestHydrationJS.ts"],"names":[],"mappings":"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"} \ No newline at end of file From 84bc476e716653af44c50f4e5f317ea8e74871cd Mon Sep 17 00:00:00 2001 From: jordansoltman Date: Sun, 12 May 2019 15:46:35 -0700 Subject: [PATCH 25/25] bump node version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eea5def..56973fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nesthydrationjs", - "version": "1.0.6", + "version": "1.1.0", "description": "Provides nested objects from tabular data.", "keywords": [ "Hydration",