Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

treeize

Package Overview
Dependencies
Maintainers
1
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

treeize - npm Package Compare versions

Comparing version 2.0.3 to 2.1.0

benchmark/index.js

422

lib/treeize.js

@@ -1,6 +0,46 @@

var util = require('util');
var _ = require('lodash');
var inflection = require('inflection');
var merge = require('object-merge');
var inflection = require('inflection')
var merge = require('object-merge')
var isArray = function(item) {
return typeof item === 'object' && item.length !== undefined
}
var isEmpty = function(item) {
return !item || (typeof item === 'object' && !Object.keys(item).length)
}
var where = function(collection, props) {
return collection.map(item => {
for (var attribute in props) {
let value = props[attribute]
if (item[attribute] !== value) {
return false
}
}
return true
})
}
var findWhere = function(collection, props) {
let matches = collection.filter(item => {
for (var attribute in props) {
let value = props[attribute]
if (item[attribute] !== value) {
return false
}
}
return true
})
if (!matches.length) {
return false
}
return matches[0]
}
function Treeize(options) {

@@ -19,3 +59,3 @@ this.baseOptions = {

log: false,
};
}

@@ -29,3 +69,3 @@ this.data = {

tree: [],
};
}

@@ -39,12 +79,12 @@ this.stats = {

sources: 0,
};
}
// set default options (below)
this.resetOptions();
this.resetOptions()
if (options) {
this.options(options);
this.options(options)
}
return this;
return this
}

@@ -54,19 +94,19 @@

if (this._options.log) {
console.log.apply(this, arguments);
console.log.apply(this, arguments)
}
return this;
};
return this
}
Treeize.prototype.getData = function() {
return this.data.tree;
};
return this.data.tree
}
Treeize.prototype.getSeedData = function() {
return this.data.seed;
};
return this.data.seed
}
Treeize.prototype.getStats = function() {
return this.stats;
};
return this.stats
}

@@ -79,65 +119,65 @@ /*

if (!row) {
return this.data.signature;
return this.data.signature
}
// start timer
var t1 = (new Date()).getTime();
var t1 = (new Date()).getTime()
// sets the signature as fixed (or not) when manually set
this.data.signature.isFixed = auto !== true;
this.data.signature.isFixed = auto !== true
var nodes = this.data.signature.nodes = [];
var isArray = _.isArray(row);
var opt = merge(this._options, options || {});
var nodes = this.data.signature.nodes = []
var isRowAnArray = isArray(row)
var opt = merge(this._options, options || {})
this.data.signature.type = isArray ? 'array' : 'object';
this.data.signature.type = isArray ? 'array' : 'object'
_.each(row, function(value, key) {
// set up attribute
for (var key in row) {
let value = row[key]
var attr = {}
attr.key = typeof key === 'number' ? key : key;//.replace(/^[\*\-\+]|[\*\-\+]$/g,'');
attr.fullPath = isArray ? value : key;
attr.split = attr.fullPath.split(opt.input.delimiter);
attr.path = attr.split.slice(0,attr.split.length-1).join(opt.input.delimiter);
attr.parent = attr.split.slice(0,attr.split.length-2).join(opt.input.delimiter);//.replace(/^[\*\-\+]|[\*\-\+]$/g,'');
attr.node = attr.split[attr.split.length - 2];
attr.attr = attr.split[attr.split.length - 1];
attr.key = typeof key === 'number' ? key : key//.replace(/^[\*\-\+]|[\*\-\+]$/g,'')
attr.fullPath = isRowAnArray ? value : key
attr.split = attr.fullPath.split(opt.input.delimiter)
attr.path = attr.split.slice(0,attr.split.length-1).join(opt.input.delimiter)
attr.parent = attr.split.slice(0,attr.split.length-2).join(opt.input.delimiter)//.replace(/^[\*\-\+]|[\*\-\+]$/g,'')
attr.node = attr.split[attr.split.length - 2]
attr.attr = attr.split[attr.split.length - 1]
if (attr.attr.match(/\*/gi)) {
attr.attr = attr.attr.replace(/[\*]/gi,'');
attr.pk = true;
attr.attr = attr.attr.replace(/[\*]/gi,'')
attr.pk = true
}
if (attr.pk) {
this.log('primary key detected in node "' + attr.attr + '"');
}
// if (attr.pk) {
// this.log('primary key detected in node "' + attr.attr + '"')
// }
// set up node reference
var node = _.findWhere(nodes, { path: attr.path });
var node = findWhere(nodes, { path: attr.path })
if (!node) {
node = { path: attr.path, attributes: [], blueprint: [] };
nodes.push(node);
node = { path: attr.path, attributes: [], blueprint: [] }
nodes.push(node)
}
node.isCollection = !attr.node || (opt.input.detectCollections && inflection.pluralize(attr.node) === attr.node);
node.isCollection = !attr.node || (opt.input.detectCollections && inflection.pluralize(attr.node) === attr.node)
var collectionFlag = attr.node && attr.node.match(/^[\-\+]|[\-\+]$/g);
var collectionFlag = attr.node && attr.node.match(/^[\-\+]|[\-\+]$/g)
if (collectionFlag) {
this.log('collection flag "' + collectionFlag + '" detected in node "' + attr.node + '"');
node.flags = true;
node.isCollection = attr.node.match(/^\+|\+$/g);
attr.node = attr.node.replace(/^[\*\-\+]|[\*\-\+]$/g,''); // clean node
//this.log('collection flag "' + collectionFlag + '" detected in node "' + attr.node + '"')
node.flags = true
node.isCollection = attr.node.match(/^\+|\+$/g)
attr.node = attr.node.replace(/^[\*\-\+]|[\*\-\+]$/g,'') // clean node
}
node.name = attr.node;
node.depth = attr.split.length - 1;
node.parent = attr.split.slice(0, attr.split.length - 2).join(opt.input.delimiter);
node.attributes.push({ name: attr.attr, key: attr.key });
node.name = attr.node
node.depth = attr.split.length - 1
node.parent = attr.split.slice(0, attr.split.length - 2).join(opt.input.delimiter)
node.attributes.push({ name: attr.attr, key: attr.key })
if (attr.pk) {
this.log('adding node to blueprint');
node.flags = true;
node.blueprint.push({ name: attr.attr, key: attr.key });
//this.log('adding node to blueprint')
node.flags = true
node.blueprint.push({ name: attr.attr, key: attr.key })
}
}, this);
}

@@ -147,63 +187,62 @@ // backfill blueprint when not specifically defined

if (!node.blueprint.length) {
node.blueprint = node.attributes;
node.blueprint = node.attributes
}
});
})
nodes.sort(function(a, b) { return a.depth < b.depth ? -1 : 1; });
nodes.sort(function(a, b) { return a.depth < b.depth ? -1 : 1 })
// end timer and add time
var t2 = ((new Date()).getTime() - t1);
this.stats.time.signatures += t2;
this.stats.time.total += t2;
var t2 = ((new Date()).getTime() - t1)
this.stats.time.signatures += t2
this.stats.time.total += t2
return this;
};
return this
}
Treeize.prototype.getSignature = function() {
return this.signature();
};
return this.signature()
}
Treeize.prototype.setSignature = function(row, options) {
return this.signature(row, options);
};
return this.signature(row, options)
}
Treeize.prototype.setSignatureAuto = function(row, options) {
return this.signature(row, options, true);
};
return this.signature(row, options, true)
}
Treeize.prototype.clearSignature = function() {
this.data.signature = { nodes: [], type: null };
this.data.signature.isFixed = false;
this.data.signature = { nodes: [], type: null }
this.data.signature.isFixed = false
return this;
};
return this
}
Treeize.prototype.grow = function(data, options) {
var opt = merge(this._options, options || {});
var opt = merge(this._options, options || {})
// chain past if no data to grow
if (!data || !_.isArray(data) || !data.length) {
return this;
if (typeof data !== 'object' || !data.length) {
return this
}
this.log('OPTIONS>', opt);
//this.log('OPTIONS>', opt)
// locate existing signature (when sharing signatures between data sources)
var signature = this.getSignature();
var signature = this.getSignature()
// set data uniformity (locally) to true to avoid signature fetching on data rows
if (_.isArray(data[0])) {
opt.input.uniformRows = true;
if (isArray(data[0])) {
opt.input.uniformRows = true
}
if (!signature.nodes.length) {
this.log('setting signature from first row of data (auto)');
//this.log('setting signature from first row of data (auto)')
// set signature from first row
signature = this.setSignatureAuto(data[0], options).getSignature();
signature = this.setSignatureAuto(data[0], options).getSignature()
// remove header row in flat array data (avoids processing headers as actual values)
if (_.isArray(data[0])) {
var originalData = data;
data = [];
if (isArray(data[0])) {
var originalData = data
data = []

@@ -213,95 +252,97 @@ // copy data without original signature row before processing

if (index > 0) {
data.push(row);
data.push(row)
}
});
})
}
}
if (opt.output.resultsAsObject && _.isArray(this.data.tree)) {
this.data.tree = {};
if (opt.output.resultsAsObject && isArray(this.data.tree)) {
this.data.tree = {}
}
this.log('SIGNATURE>', util.inspect(this.getSignature(), false, null));
//this.log('SIGNATURE>', util.inspect(this.getSignature(), false, null))
this.stats.sources++;
var t1 = (new Date()).getTime();
this.stats.sources++
var t1 = (new Date()).getTime()
data.forEach(function(row) {
this.data.seed.push(row);
var trails = {}; // LUT for trails (find parent of new node in trails path)
var trail = base = this.data.tree; // OPTIMIZATION: do we need to reset this trail for each row?
this.log('CURRENT TRAIL STATUS>', trail);
var t = null;
this.data.seed.push(row)
var trails = {} // LUT for trails (find parent of new node in trails path)
var trail = base = this.data.tree // OPTIMIZATION: do we need to reset this trail for each row?
//this.log('CURRENT TRAIL STATUS>', trail)
var t = null
// set initial base object path for non-array datasets
if (opt.output.resultsAsObject) {
trails[''] = trail;
trails[''] = trail
}
if (!this.data.signature.isFixed && !opt.input.uniformRows) {
this.log('setting signature from new row of data (auto)');
//this.log('setting signature from new row of data (auto)')
// get signature from each row
this.setSignatureAuto(row, opt);
this.log('SIGNATURE>', util.inspect(this.getSignature(), false, null));
this.setSignatureAuto(row, opt)
//this.log('SIGNATURE>', util.inspect(this.getSignature(), false, null))
}
this.stats.rows++;
this.stats.rows++
if (_.where(this.signature().nodes, { flags: true }).length) {
if (where(this.signature().nodes, { flags: true }).length) {
// flags detected within signature, clean attributes of row
_.each(row, function(value, key) {
for (var key in row) {
let value = row[key]
if (typeof key === 'string') {
var clean = key.replace(/^[\*\-\+]|[\*\-\+]$/g,'');
var clean = key.replace(/^[\*\-\+]|[\*\-\+]$/g,'')
if (clean !== key) {
this.log('cleaning key "' + key + '" and embedding as "' + clean + '"');
row[key.replace(/^[\*\-\+]|[\*\-\+]$/g,'')] = value; // simply embed value at clean path (if not already)
//this.log('cleaning key "' + key + '" and embedding as "' + clean + '"')
row[key.replace(/^[\*\-\+]|[\*\-\+]$/g,'')] = value // simply embed value at clean path (if not already)
}
}
}, this);
}
}
this.signature().nodes.forEach(function(node) {
this.log('PROCESSING NODE>', node);
var blueprint = {};
var blueprintExtended = {};
//this.log('PROCESSING NODE>', node)
var blueprint = {}
var blueprintExtended = {}
// create blueprint for locating existing nodes
node.blueprint.forEach(function(attribute) {
var key = (node.path ? (node.path + ':') : '') + attribute.name;
blueprint[attribute.name] = row[attribute.key];
this.log('creating attribute "' + attribute.name + '" within blueprint', row[attribute.key]);
}, this);
var key = (node.path ? (node.path + ':') : '') + attribute.name
blueprint[attribute.name] = row[attribute.key]
//this.log('creating attribute "' + attribute.name + '" within blueprint', row[attribute.key])
}, this)
// create full node signature for insertion/updating
node.attributes.forEach(function(attribute) {
var key = (node.path ? (node.path + ':') : '') + attribute.name;
var value = row[attribute.key];
var key = (node.path ? (node.path + ':') : '') + attribute.name
var value = row[attribute.key]
// insert extended blueprint attributes when not empty (or not pruning)
if (!opt.output.prune || (value !== null && value !== undefined)) {
this.log('creating attribute "' + attribute.name + '" within extended blueprint', row[attribute.key]);
blueprintExtended[attribute.name] = row[attribute.key];
//this.log('creating attribute "' + attribute.name + '" within extended blueprint', row[attribute.key])
blueprintExtended[attribute.name] = row[attribute.key]
}
}, this);
}, this)
this.log('EXTENDED BLUEPRINT>', blueprintExtended);
this.log('BLUEPRINT>', blueprint);
//this.log('EXTENDED BLUEPRINT>', blueprintExtended)
//this.log('BLUEPRINT>', blueprint)
// ONLY INSERT IF NOT PRUNED
if (!opt.output.prune || !_.isEmpty(blueprintExtended)) {
if (!opt.output.prune || !isEmpty(blueprintExtended)) {
// IF 0 DEPTH AND RESULTSASOBJECT, EXTEND base
if (opt.output.resultsAsObject && node.depth === 0) {
_.extend(trails[node.path] = trail = base, blueprintExtended);
this.log('extending blueprint onto base>', trail);
Object.assign(trails[node.path] = trail = base, blueprintExtended)
//this.log('extending blueprint onto base>', trail)
// IF base TRAIL IS NOT YET MAPPED
} else if (node.isCollection && !(trail = trails[node.parent])) {
this.log('PARENT TRAIL NOT FOUND (base?)');
//this.log('PARENT TRAIL NOT FOUND (base?)')
// set up target node if doesn't exist
if (!(trail = _.findWhere(base, blueprint))) {
base.push(trail = blueprintExtended);
if (!(trail = findWhere(base, blueprint))) {
base.push(trail = blueprintExtended)
} else {
_.extend(trail, blueprintExtended);
Object.assign(trail, blueprintExtended)
}
trails[node.path] = trail;
trails[node.path] = trail

@@ -313,16 +354,16 @@ // NORMAL NODE TRAVERSAL

// handle collection nodes
this.log('inserting into collection node', trail);
//this.log('inserting into collection node', trail)
if (!trail[node.name]) {
// node attribute doesnt exist, create array with fresh blueprint
trail[node.name] = [blueprintExtended];
trails[node.path] = blueprintExtended;
trail[node.name] = [blueprintExtended]
trails[node.path] = blueprintExtended
} else {
// node attribute exists, find or inject blueprint
var t;
if (!(t = _.findWhere(trail[node.name], blueprint))) {
trail[node.name].push(trail = blueprintExtended);
var t
if (!(t = findWhere(trail[node.name], blueprint))) {
trail[node.name].push(trail = blueprintExtended)
} else {
_.extend(t, blueprintExtended);
Object.assign(t, blueprintExtended)
}
trails[node.path] = t || trail;
trails[node.path] = t || trail
}

@@ -332,6 +373,6 @@ } else {

if (trail == base && node.parent === '') {
base.push(trails[node.parent] = trail = {});
this.log('base insertion');
base.push(trails[node.parent] = trail = {})
//this.log('base insertion')
}
trail = trails[node.parent];
trail = trails[node.parent]

@@ -343,50 +384,51 @@ // ON DEEP NODES, THE PARENT WILL BE TOO LONG AND FAIL ON THE NEXT IF STATEMENT BELOW

// backtrack from parent trail segments until trail found, then create creadcrumbs
var breadcrumbs = [];
var segments = node.parent.split(':');
var pathAttempt = node.parent;
var segmentsStripped = 0;
var breadcrumbs = []
var segments = node.parent.split(':')
var numSegments = segments.length
var pathAttempt = node.parent
var segmentsStripped = 0
this.log('path MISSING for location "' + pathAttempt + '"');
//this.log('path MISSING for location "' + pathAttempt + '"')
while (!(trail = trails[pathAttempt])) {
segmentsStripped++;
pathAttempt = _.initial(segments, segmentsStripped).join(':');
this.log('..attempting path location for "' + pathAttempt + '"');
segmentsStripped++
pathAttempt = segments.slice(0,numSegments-segmentsStripped).join(':')
//this.log('..attempting path location for "' + pathAttempt + '"')
//infinite loop kickout
if (segmentsStripped > 15) break;
if (segmentsStripped > 15) break
}
this.log('path FOUND for location for "' + pathAttempt + '" after removing ' + segmentsStripped + ' segments');
//this.log('path FOUND for location for "' + pathAttempt + '" after removing ' + segmentsStripped + ' segments')
// create stored nodes if they don't exist.
_.each(_.rest(segments, segments.length - segmentsStripped), function(segment) {
var isCollection = ((inflection.pluralize(segment) === segment) || segment.match(/^\+|\+$/)) && (!segment.match(/^\-|\-$/));
segments.slice(numSegments - segmentsStripped).forEach(function(segment) {
var isCollection = ((inflection.pluralize(segment) === segment) || segment.match(/^\+|\+$/)) && (!segment.match(/^\-|\-$/))
// TODO: add modifier detection
this.log('creating or trailing path segment ' + (isCollection ? '[collection]' : '{object}') + ' "' + segment + '"');
//this.log('creating or trailing path segment ' + (isCollection ? '[collection]' : '{object}') + ' "' + segment + '"')
segment = segment.replace(/^[\*\-\+]|[\*\-\+]$/g,'');
segment = segment.replace(/^[\*\-\+]|[\*\-\+]$/g,'')
if (isCollection) {
// retrieve or set collection segment and push new trail onto it
(trail[segment] = trail[segment] || []).push(trail = {});
(trail[segment] = trail[segment] || []).push(trail = {})
} else {
trail = trail[segment] = trail[segment] || {};
trail = trail[segment] = trail[segment] || {}
}
}, this);
})
}
this.log('inserting into non-collection node');
//this.log('inserting into non-collection node')
//if (!trail[node.name]) { // TODO: CONSIDER: add typeof check to this for possible overwriting
if (!trail[node.name] || (opt.output.objectOverwrite && (typeof trail[node.name] !== typeof blueprintExtended))) {
// node attribute doesnt exist, create object
this.log('create object');
trail[node.name] = blueprintExtended;
trails[node.path] = blueprintExtended;
//this.log('create object')
trail[node.name] = blueprintExtended
trails[node.path] = blueprintExtended
} else {
// node attribute exists, set path for next pass
// TODO: extend trail??
this.log('object at node "' + node.name + '" exists as "' + trail[node.name] + '", skipping insertion and adding trail');
//this.log('object at node "' + node.name + '" exists as "' + trail[node.name] + '", skipping insertion and adding trail')
if (typeof trail[node.name] === 'object') {
trail[node.name] = merge(trail[node.name], blueprintExtended);
trail[node.name] = merge(trail[node.name], blueprintExtended)
}
this.log('trail[node.name] updated to "' + trail[node.name]);
trails[node.path] = trail[node.path];
//this.log('trail[node.name] updated to "' + trail[node.name])
trails[node.path] = trail[node.path]
}

@@ -397,15 +439,15 @@ }

}
}, this);
}, this);
}, this)
}, this)
var t2 = ((new Date()).getTime() - t1);
this.stats.time.total += t2;
var t2 = ((new Date()).getTime() - t1)
this.stats.time.total += t2
// clear signature between growth sets - TODO: consider leaving this wipe pass off if processing multiple identical sources (add)
if (!signature.isFixed) {
this.signature([]);
this.signature([])
}
return this;
};
return this
}

@@ -420,28 +462,28 @@ /*

if (!options) {
return merge({}, this._options);
return merge({}, this._options)
}
this._options = merge(this._options, options);
this._options = merge(this._options, options)
return this;
};
return this
}
Treeize.prototype.getOptions = function() {
return this._options;
};
return this._options
}
Treeize.prototype.setOptions = function(options) {
return this.options(options);
};
return this.options(options)
}
Treeize.prototype.resetOptions = function() {
this._options = merge({}, this.baseOptions);
this._options = merge({}, this.baseOptions)
return this;
};
return this
}
Treeize.prototype.toString = function treeToString() {
return util.inspect(this.data.tree, false, null);
};
return 'WARNING: .toString() method of Treeize is deprecated'
}
module.exports = Treeize;
module.exports = Treeize
{
"name": "treeize",
"version": "2.0.3",
"version": "2.1.0",
"description": "Converts tabular row data (as from SQL joins, flat JSON, etc) to deep object graphs based on simple column naming conventions - without the use of an ORM or models.",

@@ -29,3 +29,3 @@ "main": "./lib/treeize.js",

],
"author": "K. R. Whitley <kevin@krwhitley.com> (http://krwhitley.com/)",
"author": "Kevin R. Whitley <kevin3503@gmail.com> (http://krwhitley.com/)",
"licenses": [

@@ -41,19 +41,16 @@ {

"dependencies": {
"lodash": "~1.3.1",
"inflection": "~1.2.6",
"object-merge": "~2.5.1"
"inflection": "^1.12.0",
"object-merge": "^2.5.1"
},
"devDependencies": {
"grunt": "latest",
"grunt-cli": "latest",
"benchmark": "^2.1.4",
"mocha": "latest",
"should": "latest",
"grunt-contrib-jshint": "latest",
"grunt-contrib-nodeunit": "latest",
"grunt-contrib-watch": "latest",
"grunt-mocha-test": "~0.12.0"
"nodemon": "^1.11.0",
"should": "latest"
},
"scripts": {
"test": "node_modules/.bin/grunt test"
"test": "mocha",
"test:watch": "mocha --watch test/test.js .",
"benchmark": "npm run test:watch & nodemon --watch . benchmark/index.js"
}
}

@@ -134,4 +134,2 @@ # Treeize.js

- [`getStats()`](#getStats) - returns object with growth statistics
- [`toString()`](#toString) - uses `util` to return data in visually formatted object graph
- [`log(arg1, arg2, arg3)`](#log) - console.log output of `arg1..n` when `log` option is set to `true`

@@ -160,4 +158,3 @@ # API

resultsAsObject: false, // root structure defaults to array (instead of object)
},
log: false, // enable logging
}
}

@@ -169,3 +166,3 @@ ```

```js
.setOptions({ log: true, input: { delimiter: '|' }});
.setOptions({ input: { delimiter: '|' }});
```

@@ -198,6 +195,2 @@

`log`<a name="optionsLog" />
Setting to true enables traversal information to be logged to console during growth process.
### .getOptions()<a name="getOptions" />

@@ -295,35 +288,2 @@

### .toString()<a name="toString" />
Typecasting a treeize instance to String or manually calling `.toString()` on it will output it visually using the `util` library from node.js.
```js
var tree = new Treeize();
tree.grow(data);
// use automatic typecasting to trigger
console.log(tree + '');
// or call manually
console.log(tree.toString());
```
### .log(arg1, arg2, ...)<a name="log" />
Equivalent to `console.log(arg1, arg2, ...)` when the `log` option is set to `true`. Useful for debugging messages or visual output that you can toggle from a single source.
```js
var tree = new Treeize();
tree.log('my message');
// 'my message' will NOT be written to the console
tree
.setOptions({ log: true })
.log('my message')
;
// 'my message' WILL be written to the console
```
---

@@ -690,1 +650,2 @@

- **2.0.3** - internal variable renaming to avoid deprecation error
- **2.1.0** - major (> 3x) performance improvement - required dropping support for .toString() and internal logging, removed lodash as a dependency
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc