node-object-hash
Advanced tools
+127
| /** | ||
| * Created on 8/23/16. | ||
| */ | ||
| 'use strict'; | ||
| const faker = require('faker'); | ||
| const data = []; | ||
| const datacount = 100000; | ||
| const objectHash = require('object-hash'); | ||
| const nodeObjectHash = require('../hash'); | ||
| const nodeObjectHash2 = require('../hash2')(); | ||
| console.log('Creating fake data...'); | ||
| for (let i = 0; i < datacount; i++){ | ||
| data.push({ | ||
| name: faker.name.firstName(), | ||
| date: new Date(), | ||
| address: { | ||
| city: faker.address.city(), | ||
| streetAddress: faker.address.streetAddress(), | ||
| country: faker.address.country() | ||
| }, | ||
| email: [ | ||
| faker.internet.email(), | ||
| faker.internet.email(), | ||
| faker.internet.email(), | ||
| faker.internet.email() | ||
| ], | ||
| randoms: [ | ||
| faker.random.number(), | ||
| faker.random.alphaNumeric(), | ||
| faker.random.number(), | ||
| faker.random.alphaNumeric(), | ||
| faker.random.words(), | ||
| faker.random.word() | ||
| ], | ||
| avatars: [ | ||
| { | ||
| number: faker.random.number(), | ||
| avatar: faker.internet.avatar() | ||
| },{ | ||
| number: faker.random.number(), | ||
| avatar: faker.internet.avatar() | ||
| },{ | ||
| number: faker.random.number(), | ||
| avatar: faker.internet.avatar() | ||
| },{ | ||
| number: faker.random.number(), | ||
| avatar: faker.internet.avatar() | ||
| } | ||
| ] | ||
| }); | ||
| } | ||
| var memStats; | ||
| var memMaxHeap = 0; | ||
| var memHeapStart; | ||
| var data1 = data.slice(0); | ||
| var data2 = data.slice(0); | ||
| var data3 = data.slice(0); | ||
| console.log('Starting benchmark...'); | ||
| memStats = process.memoryUsage(); | ||
| memHeapStart = memStats.heapUsed; | ||
| console.time('node-object-hash-2'); | ||
| data1.forEach((it, idx) => { | ||
| it.hash = nodeObjectHash2.hash(it); | ||
| if (idx % 10000 === 0) { | ||
| memStats = process.memoryUsage(); | ||
| if (memMaxHeap < memStats.heapUsed) { | ||
| memMaxHeap = memStats.heapUsed; | ||
| } | ||
| console.log(`${idx} items processed: RSS:${Math.round(memStats.rss / (1024*1024))}Mb / HEAP: ${Math.round(memStats.heapUsed / (1024*1024))}Mb`) | ||
| } | ||
| }); | ||
| console.timeEnd('node-object-hash-2'); | ||
| memStats = process.memoryUsage(); | ||
| memMaxHeap = memMaxHeap < memStats.heapUsed ? memStats.heapUsed : memMaxHeap; | ||
| console.log(`Memory footprint:\n MAX HEAP DIFF:${Math.round((memMaxHeap-memHeapStart) / (1024*1024))}Mb`); | ||
| console.log('Collecting garbage...'); | ||
| gc(); | ||
| memMaxHeap = 0; | ||
| memStats = process.memoryUsage(); | ||
| memHeapStart = memStats.heapUsed; | ||
| console.time('node-object-hash-1'); | ||
| data2.forEach((it, idx) => { | ||
| it.hash = nodeObjectHash.hash(it); | ||
| if (idx % 10000 === 0) { | ||
| memStats = process.memoryUsage(); | ||
| if (memMaxHeap < memStats.heapUsed) { | ||
| memMaxHeap = memStats.heapUsed; | ||
| } | ||
| console.log(`${idx} items processed: RSS:${Math.round(memStats.rss / (1024*1024))}Mb / HEAP: ${Math.round(memStats.heapUsed / (1024*1024))}Mb`) | ||
| } | ||
| }); | ||
| console.timeEnd('node-object-hash-1'); | ||
| memStats = process.memoryUsage(); | ||
| memMaxHeap = memMaxHeap < memStats.heapUsed ? memStats.heapUsed : memMaxHeap; | ||
| console.log(`Memory footprint:\n MAX HEAP DIFF:${Math.round((memMaxHeap-memHeapStart) / (1024*1024))}Mb`); | ||
| console.log('Collecting garbage...'); | ||
| gc(); | ||
| memMaxHeap = 0; | ||
| memStats = process.memoryUsage(); | ||
| memHeapStart = memStats.heapUsed; | ||
| console.time('object-hash'); | ||
| data3.forEach((it, idx) => { | ||
| it.hash = objectHash(it, {algorithm: 'sha256', encoding: 'hex', unorderedArrays: true}); | ||
| if (idx % 10000 === 0) { | ||
| memStats = process.memoryUsage(); | ||
| if (memMaxHeap < memStats.heapUsed) { | ||
| memMaxHeap = memStats.heapUsed; | ||
| } | ||
| console.log(`${idx} items processed: RSS:${Math.round(memStats.rss / (1024*1024))}Mb / HEAP: ${Math.round(memStats.heapUsed / (1024*1024))}Mb`) | ||
| } | ||
| }); | ||
| console.timeEnd('object-hash'); | ||
| memStats = process.memoryUsage(); | ||
| memMaxHeap = memMaxHeap < memStats.heapUsed ? memStats.heapUsed : memMaxHeap; | ||
| console.log(`Memory footprint:\n MAX HEAP DIFF:${Math.round((memMaxHeap-memHeapStart) / (1024*1024))}Mb`); |
+88
| /** | ||
| * Node.js constant object hash module. | ||
| * @exports node-object-hash | ||
| * @type {Function} | ||
| */ | ||
| 'use strict'; | ||
| const crypto = require('crypto'); | ||
| /** | ||
| * Sorts object fields | ||
| * @param {Object|Array|string|function} obj Initial object | ||
| * @param {Object} options Options | ||
| * @param {boolean} [options.coerce=true] Perform coercion | ||
| * @param {boolean} [options.sort=true] Perform Array, Set, Object sorting | ||
| * @returns {string} | ||
| */ | ||
| exports.sortedObjectString = (obj, options) => { | ||
| const coerce = typeof options.coerce == 'undefined' ? true : options.coerce; | ||
| const sort = typeof options.sort == 'undefined' ? true : options.sort; | ||
| if (typeof obj == 'object') { | ||
| if (Array.isArray(obj)) { | ||
| let tmp = [...obj]; | ||
| tmp.forEach((it, idx) => { | ||
| tmp[idx] = exports.sortedObjectString(it, {coerce, sort}) | ||
| }); | ||
| return sort ? `[${tmp.sort().toString()}]` : `[${tmp.toString()}]`; | ||
| } | ||
| if (obj === null) { | ||
| return `null`; | ||
| } | ||
| if (obj instanceof Map) { | ||
| return `Map[${[...obj].toString()}]`; | ||
| } | ||
| if (obj instanceof Set) { | ||
| return sort ? `Set[${[...obj].sort().toString()}]` : `Set[${[...obj].toString()}]`; | ||
| } | ||
| const sortedObj = new Map(); | ||
| const keys = sort ? Object.keys(obj).sort() : Object.keys(obj); | ||
| keys.forEach((key) => { | ||
| sortedObj.set(key, exports.sortedObjectString(obj[key], {coerce, sort})); | ||
| }); | ||
| return `{${[...sortedObj].toString()}}`; | ||
| } | ||
| if (typeof obj == 'function') { | ||
| return `${obj.name}=>${obj.toString()}`; | ||
| } | ||
| if (coerce) { | ||
| if (typeof obj == 'boolean') { | ||
| return `${Number(obj)}`; | ||
| } | ||
| } else { | ||
| if (typeof obj == 'string') { | ||
| return `"${obj}"` | ||
| } | ||
| } | ||
| if (coerce && typeof obj == 'boolean') { | ||
| return `${Number(obj)}`; | ||
| } | ||
| return obj; | ||
| }; | ||
| /** | ||
| * Calculates object hash | ||
| * @param {Object} obj Object to hash | ||
| * @param {Object} [options] Options | ||
| * @param {string} [options.alg="sha256"] Crypto algorithm to use | ||
| * @param {string} [options.enc="hex"] Hash string encoding | ||
| * @param {boolean} [options.coerce=true] Perform coercion | ||
| * @param {boolean} [options.sort=true] Perform Array, Set, Object sorting | ||
| * @returns {string} Hash string | ||
| */ | ||
| exports.hash = (obj, options) => { | ||
| options = options || {}; | ||
| const alg = options.alg || 'sha256'; | ||
| const enc = options.enc || 'hex'; | ||
| const coerce = typeof options.coerce == 'undefined' ? true : options.coerce; | ||
| const sort = typeof options.sort == 'undefined' ? true : options.sort; | ||
| if (~crypto.getHashes().indexOf(alg)) { | ||
| const sorted = exports.sortedObjectString(obj, {coerce, sort}); | ||
| return crypto.createHash(alg) | ||
| .update(sorted) | ||
| .digest(enc); | ||
| } else { | ||
| throw new Error(`Algorithm ${alg} not supported by "ctypto" module`); | ||
| } | ||
| }; |
+63
| /** | ||
| * Created on 8/23/16. | ||
| */ | ||
| 'use strict'; | ||
| var crypto = require('crypto'); | ||
| var objectSorter = require('./objectSorter'); | ||
| /** | ||
| * @typedef {Object} API | ||
| * @property {Function} hash Returns object hash | ||
| * @property {Function} sortObject Returns sorted object string | ||
| */ | ||
| /** | ||
| * API constructor | ||
| * @param {Object} [options] Library options | ||
| * @param {boolean} [options.coerce="true"] Performs type coercion | ||
| * @param {boolean} [options.sort="true"] Performs array, object, etc. sorting | ||
| * @param {string} [options.alg="sha256"] Default crypto algorithm to use (can be overridden) | ||
| * @param {string} [options.enc="hex"] Hash string encoding (can be overridden) | ||
| * @returns {API} API object | ||
| */ | ||
| module.exports = function Hash(options) { | ||
| var api = {}; | ||
| var defaults = options || {}; | ||
| defaults.coerce = typeof defaults.coerce === 'undefined' ? true : defaults.coerce; | ||
| defaults.sort = typeof defaults.sort === 'undefined' ? true : defaults.sort; | ||
| defaults.alg = defaults.alg || 'sha256'; | ||
| defaults.enc = defaults.enc || 'hex'; | ||
| var sortObjectToString = objectSorter(defaults); | ||
| /** | ||
| * Creates hash from given object | ||
| * @param {*} obj JS object to hash | ||
| * @param {Object} [opts] Options | ||
| * @param {string} [opts.alg="sha256"] Crypto algorithm to use | ||
| * @param {string} [opts.enc="hex"] Hash string encoding | ||
| * @returns {string} Object hash value | ||
| */ | ||
| api.hash = function hash(obj, opts) { | ||
| opts = opts || {}; | ||
| var alg = opts.alg || defaults.alg; | ||
| var enc = opts.enc || defaults.enc; | ||
| var sorted = sortObjectToString(obj); | ||
| return crypto.createHash(alg) | ||
| .update(sorted) | ||
| .digest(enc); | ||
| }; | ||
| /** | ||
| * Creates sorted string from object | ||
| * @param {*} obj JS object to be sorted | ||
| * @returns {string} Sorted object string | ||
| */ | ||
| api.sortObject = function sortObject(obj) { | ||
| return sortObjectToString(obj); | ||
| }; | ||
| return api; | ||
| }; |
+163
| /** | ||
| * Created on 8/22/16. | ||
| */ | ||
| 'use strict'; | ||
| /** | ||
| * Guesses object's type | ||
| * @param {Object} obj Object | ||
| * @returns {string} | ||
| */ | ||
| function guessObjectType(obj) { | ||
| var hasMapSet = typeof Map !== 'undefined'; | ||
| if (obj === null) { | ||
| return 'null'; | ||
| } | ||
| if (Array.isArray(obj)) { | ||
| return 'array'; | ||
| } | ||
| if (hasMapSet && (obj instanceof Map || obj instanceof WeakMap)) { | ||
| return 'map'; | ||
| } | ||
| if (hasMapSet && (obj instanceof Set || obj instanceof WeakSet)) { | ||
| return 'set'; | ||
| } | ||
| if (obj instanceof Date) { | ||
| return 'date'; | ||
| } | ||
| return 'object'; | ||
| } | ||
| /** | ||
| * Guesses variable type | ||
| * @param {*} obj | ||
| * @returns {string} | ||
| */ | ||
| function guessType(obj) { | ||
| var type = typeof obj; | ||
| return type !== 'object' ? type : guessObjectType(obj); | ||
| } | ||
| /** | ||
| * Creates object sorter function | ||
| * @param {Object} [options] Sorter options | ||
| * @param {string} [options.coerce="true"] Performs type coercion (e.g sorter(true) === ("1"); sorter(1) === ("1")) | ||
| * @param {string} [options.sort="true"] Performs array, object, etc. sorting | ||
| * @returns {string} Sorted object string | ||
| */ | ||
| var objectSorter = function (options) { | ||
| options = options || {}; | ||
| var coerce = typeof options.coerce === 'undefined' ? true : options.coerce; | ||
| var sort = typeof options.sort === 'undefined' ? true : options.sort; | ||
| var self = {}; | ||
| self.string = function sortString(obj) { | ||
| if (coerce) { | ||
| return obj; | ||
| } | ||
| return '<:s>:' + obj; | ||
| }; | ||
| self.number = function sortNumber(obj) { | ||
| if (coerce) { | ||
| return obj.toString(); | ||
| } | ||
| return '<:n>:' + obj; | ||
| }; | ||
| self.boolean = function sortBoolean(obj) { | ||
| if (coerce) { | ||
| return obj ? '1' : '0'; | ||
| } | ||
| return obj ? '<:b>:true' : '<:b>:false'; | ||
| }; | ||
| self.symbol = function sortSymbol() { | ||
| return '<:smbl>'; | ||
| }; | ||
| self.undefined = function sortUndefined() { | ||
| if (coerce) { | ||
| return ''; | ||
| } | ||
| return '<:undf>'; | ||
| }; | ||
| self.null = function sortNull() { | ||
| if (coerce) { | ||
| return ''; | ||
| } | ||
| return '<:null>'; | ||
| }; | ||
| self.function = function sortFunction(obj) { | ||
| if (coerce){ | ||
| return obj.name + '=>' + obj.toString(); | ||
| } | ||
| return '<:func>:' + obj.name + '=>' + obj.toString(); | ||
| }; | ||
| self.array = function sortArray(obj) { | ||
| var item, itemType; | ||
| var result = []; | ||
| for (var i = 0; i < obj.length; i++) { | ||
| item = obj[i]; | ||
| itemType = guessType(item); | ||
| result.push(self[itemType](item)); | ||
| } | ||
| return sort ? '[' + result.sort().toString() + ']' : '[' + result.toString() + ']'; | ||
| }; | ||
| self.set = function sortSet(obj) { | ||
| return self.array(Array.from(obj)); | ||
| }; | ||
| self.date = function sortDate(obj) { | ||
| var dateStr = obj.toISOString(); | ||
| if (coerce) { | ||
| return dateStr; | ||
| } | ||
| return '<:date>:' + dateStr; | ||
| }; | ||
| self.object = function sortObject(obj) { | ||
| var keys = sort ? Object.keys(obj).sort() : Object.keys(obj); | ||
| var objArray = []; | ||
| var key, value, valueType; | ||
| for (var i = 0; i < keys.length; i++) { | ||
| key = keys[i]; | ||
| value = obj[key]; | ||
| valueType = guessType(value); | ||
| objArray.push(key + ':' + self[valueType](value)) | ||
| } | ||
| return '{' + objArray.toString() + '}'; | ||
| }; | ||
| self.map = function sortMap(obj) { | ||
| var arr = Array.from(obj); | ||
| var key, value, item; | ||
| for (var i = 0; i < arr.length; i++) { | ||
| item = arr[i]; | ||
| key = item[0]; | ||
| value = item[1]; | ||
| item = [ | ||
| self[guessType(key)](key), | ||
| self[guessType(value)](value), | ||
| ]; | ||
| arr[i] = item; | ||
| } | ||
| return sort ? '[' + arr.sort().join(';') + ']' : '[' + arr.join(';') + ']'; | ||
| }; | ||
| function obj2string(obj) { | ||
| return self[guessType(obj)](obj); | ||
| } | ||
| return obj2string; | ||
| }; | ||
| module.exports = objectSorter; |
| /** | ||
| * Created on 8/23/16. | ||
| */ | ||
| 'use strict'; | ||
| const hash = require('../hash2'); | ||
| const assert = require('chai').assert; | ||
| const hashSC = hash(); | ||
| const hashS = hash({coerce: false}); | ||
| const hashC = hash({sort: false}); | ||
| const libName = 'node-object-hash-v2'; | ||
| const objects = { | ||
| a: { | ||
| b: 1, | ||
| c: 3, | ||
| a: 4, | ||
| f: 0 | ||
| }, | ||
| b: { | ||
| c: 3, | ||
| b: 1, | ||
| f: 0, | ||
| a: 4 | ||
| }, | ||
| c: { | ||
| c: '3', | ||
| b: 1, | ||
| f: '0', | ||
| a: 4 | ||
| }, | ||
| d: [4, 3, 0, 2, 1], | ||
| e: [2, '4', '3', false, true], | ||
| f: { | ||
| a: [{c: 2, a: 1, b: {a: 3, c: 2, b: 0}}], | ||
| b: [1, 'a', {}, null], | ||
| }, | ||
| g: { | ||
| b: ['a', 1, {}, undefined], | ||
| a: [{c: '2', b: {b: false, c: 2, a: '3'}, a: true}] | ||
| } | ||
| }; | ||
| describe(`${libName}`, () => { | ||
| describe(`#hash() on objects: [sort=true, coerce=true]`, () => { | ||
| it(`should return equal hashes`, () => { | ||
| assert.equal(hashSC.hash(objects.a), hashSC.hash(objects.c)); | ||
| }); | ||
| }); | ||
| describe(`#hash() on objects: [sort=true, coerce=false]`, () => { | ||
| it(`should return equal hashes`, () => { | ||
| assert.equal(hashS.hash(objects.a), hashS.hash(objects.b)); | ||
| }); | ||
| }); | ||
| describe(`#hash() on objects: [sort=false, coerce=true]`, () => { | ||
| it(`should return equal hashes`, () => { | ||
| assert.equal(hashC.hash(objects.c), hashC.hash(objects.b)); | ||
| }); | ||
| }); | ||
| describe(`#hash() on arrays: [sort=true, coerce=true]`, () => { | ||
| it(`should return equal hashes`, () => { | ||
| assert.equal(hashSC.hash(objects.d), hashSC.hash(objects.e)); | ||
| }); | ||
| }); | ||
| describe(`#hash() on complex objects: [sort=true, coerce=true]`, () => { | ||
| it(`should return equal hashes`, () => { | ||
| assert.equal(hashSC.hash(objects.f), hashSC.hash(objects.g)); | ||
| }); | ||
| }); | ||
| describe(`#sortObject() on multitype array: [sort=true, coerce=true]`, () => { | ||
| it(`should return sorted array`, () => { | ||
| assert.equal(hashSC.sortObject(objects.e), '[0,1,2,3,4]'); | ||
| }); | ||
| }); | ||
| }); |
+7
-4
| { | ||
| "name": "node-object-hash", | ||
| "version": "0.2.1", | ||
| "version": "1.0.0", | ||
| "description": "Node.js object hash library with properties/arrays sorting to provide constant hashes", | ||
| "main": "index.js", | ||
| "main": "hash2.js", | ||
| "directories": { | ||
@@ -12,6 +12,9 @@ "test": "test" | ||
| "chai": "^3.5.0", | ||
| "mocha": "^2.5.3" | ||
| "faker": "^3.1.0", | ||
| "mocha": "^2.5.3", | ||
| "object-hash": "^1.1.4" | ||
| }, | ||
| "scripts": { | ||
| "test": "mocha --harmony" | ||
| "test": "mocha --harmony", | ||
| "bench": "node --expose-gc ./bench/index.js" | ||
| }, | ||
@@ -18,0 +21,0 @@ "repository": { |
+71
-33
| # node-object-hash | ||
| Node object hash library. Built on top of node's crypto module. | ||
| Node.js object hash library with properties/arrays sorting to provide constant hashes. | ||
| Built on top of node's crypto module (so for using in browser use something | ||
| like browserify or use crypto functions polyfills). | ||
| ### Installation | ||
@@ -10,3 +13,2 @@ `npm i node-object-hash` | ||
| - Supports object property sorting for constant hashes for objects with same properties, but different order. | ||
| - NOTE: object arrays should be sorted manually if needed | ||
| - Supports ES6 Maps and Sets. | ||
@@ -20,53 +22,89 @@ - Supports type coercion (e.g. 1 and "1" will be the same) | ||
| ### Changes | ||
| #### v0.x.x -> v1.0.0 | ||
| - Sorting mechanism rewritten form ES6 Maps to simple arrays | ||
| (add <=node-4.0.0 support) | ||
| - Performance optimization (~2 times faster than 0.x.x) | ||
| - API changes: | ||
| - Now module returns 'constructor' function, where you can set | ||
| default parameters: ```var objectHash = require('node-object-hash')(options);``` | ||
| ### API | ||
| #### Constructor `require('node-object-hash')([options])` | ||
| Returns preconfigured object with API | ||
| Parameters: | ||
| * `options`:`<object>` - object with hasher config options | ||
| * `options.coerce`:`<boolean>` - if true performs type coercion (default: `true`); | ||
| e.g. `hash(true) == hash('1') == hash(1)`, `hash(false) == hash('0') == hash(0)` | ||
| * `options.sort`:`<boolean>` - if true performs sorting on objects, arrays, etc. (default: `true`); | ||
| * `options.alg`:`<string>` - sets default hash algorithm (default: `'sha256'`); can be overridden in `hash` method; | ||
| * `options.enc`:`<string>` - sets default hash encoding (default: `'hex'`); can be overridden in `hash` method; | ||
| #### `hash(object[, options])` | ||
| Returns hash string. | ||
| * `object` object for calculating hash; | ||
| * `options` object with options; | ||
| * `options.alg` hash algorithm (default: `'sha256'`); | ||
| * `options.enc` hash encoding (default: `'hex'`); | ||
| * `options.coerce` if true performs type coercion (default: `true`); | ||
| * `options.sort` if true performs sorting on objects, arrays and Sets (default: `true`); | ||
| * `object`:`<*>` object for calculating hash; | ||
| * `options`:`<object>` object with options; | ||
| * `options.alg`:`<string>` - hash algorithm (default: `'sha256'`); | ||
| * `options.enc`:`<string>` - hash encoding (default: `'hex'`); | ||
| #### `sortedObjectString(object[, options])` | ||
| #### `sortObject(object)` | ||
| Returns sorted string generated from object | ||
| * `object` object for calculating hash; | ||
| * `options` object with options; | ||
| * `options.coerce` if true performs type coercion (default: `true`); | ||
| * `options.sort` if true performs sorting on objects, arrays and Sets (default: `true`); | ||
| * `object`:`<*>` - object for sorting; | ||
| ### Requirements | ||
| - `>=nodejs-0.10.0` (starting from version 1.0.0) | ||
| - `>=nodejs-6.0.0` | ||
| - `>=nodejs-4.0.0` (requires to run node with `--harmony` flag) | ||
| - nodejs `crypto` module | ||
| ### Example | ||
| ```js | ||
| const {hash} = require('node-object-hash'); | ||
| var hasher = require('node-object-hash'); | ||
| const object = { | ||
| x: new Map([['a', 1], ['c', 3], ['b', 2]]), | ||
| z: new Set([4,3,2,1]), | ||
| f: 2, | ||
| e: [{a:2}, 3, {g:2, e:{d:1}}, "rt", "1", "2abc", "3d"], | ||
| d: [3,5,6,1,2,3], | ||
| c: "3ab", | ||
| b: "2", | ||
| a: 1 | ||
| var hashSortCoerce = hasher({sort:true, coerce:true}); | ||
| // or | ||
| // var hashSortCoerce = hasher(); | ||
| // or | ||
| // var hashSort = hasher({sort:true, coerce:false}); | ||
| // or | ||
| // var hashCoerce = hasher({sort:false, coerce:true}); | ||
| var objects = { | ||
| a: { | ||
| a: [{c: 2, a: 1, b: {a: 3, c: 2, b: 0}}], | ||
| b: [1, 'a', {}, null], | ||
| }, | ||
| b: { | ||
| b: ['a', 1, {}, undefined], | ||
| a: [{c: '2', b: {b: false, c: 2, a: '3'}, a: true}] | ||
| }, | ||
| c: ['4', true, 0, 2, 3] | ||
| }; | ||
| const hashString = hash(object); | ||
| const hashStringSha512 = hash(object, {alg:'sha512'}); | ||
| hashSortCoerce.hash(objects.a) === hashSortCoerce.hash(objects.b); | ||
| // returns true | ||
| hashSortCoerce.sortObject(object.c) | ||
| // returns '[0,1,2,3,4]' | ||
| ``` | ||
| ### Compared to same libraries | ||
| ### Benchmark results | ||
| Bench data - array of 100000 complex objects | ||
| #### Usage | ||
| `npm run bench` | ||
| #### Results | ||
| | | node-object-hash-0.2.1 | node-object-hash-1.0.0 | object-hash-1.1.4 | | ||
| |---|---|---|---| | ||
| | Time | 5773.869ms | 2961.812ms | 534528.254ms | | ||
| | Memory | ~35Mb | ~33Mb | ~41Mb | | ||
| ### Similar libraries | ||
| * https://www.npmjs.com/package/object-hash | ||
| * Pros: | ||
| * node-object-hash has twice smaller memory footprint | ||
| * five times faster | ||
| * ~~no memory leak when using in sync flow~~ (fixed in last version of `object-hash`) | ||
| * Cons: | ||
| * Only `>=node-4.0.0` supported | ||
| ### License | ||
| ISC |
-88
| /** | ||
| * Node.js constant object hash module. | ||
| * @exports node-object-hash | ||
| * @type {Function} | ||
| */ | ||
| 'use strict'; | ||
| const crypto = require('crypto'); | ||
| /** | ||
| * Sorts object fields | ||
| * @param {Object|Array|string|function} obj Initial object | ||
| * @param {Object} options Options | ||
| * @param {boolean} [options.coerce=true] Perform coercion | ||
| * @param {boolean} [options.sort=true] Perform Array, Set, Object sorting | ||
| * @returns {string} | ||
| */ | ||
| exports.sortedObjectString = (obj, options) => { | ||
| const coerce = typeof options.coerce == 'undefined' ? true : options.coerce; | ||
| const sort = typeof options.sort == 'undefined' ? true : options.sort; | ||
| if (typeof obj == 'object') { | ||
| if (Array.isArray(obj)) { | ||
| let tmp = [...obj]; | ||
| tmp.forEach((it, idx) => { | ||
| tmp[idx] = exports.sortedObjectString(it, {coerce, sort}) | ||
| }); | ||
| return sort ? `[${tmp.sort().toString()}]` : `[${tmp.toString()}]`; | ||
| } | ||
| if (obj === null) { | ||
| return `null`; | ||
| } | ||
| if (obj instanceof Map) { | ||
| return `Map[${[...obj].toString()}]`; | ||
| } | ||
| if (obj instanceof Set) { | ||
| return sort ? `Set[${[...obj].sort().toString()}]` : `Set[${[...obj].toString()}]`; | ||
| } | ||
| const sortedObj = new Map(); | ||
| const keys = sort ? Object.keys(obj).sort() : Object.keys(obj); | ||
| keys.forEach((key) => { | ||
| sortedObj.set(key, exports.sortedObjectString(obj[key], {coerce, sort})); | ||
| }); | ||
| return `{${[...sortedObj].toString()}}`; | ||
| } | ||
| if (typeof obj == 'function') { | ||
| return `${obj.name}=>${obj.toString()}`; | ||
| } | ||
| if (coerce) { | ||
| if (typeof obj == 'boolean') { | ||
| return `${Number(obj)}`; | ||
| } | ||
| } else { | ||
| if (typeof obj == 'string') { | ||
| return `"${obj}"` | ||
| } | ||
| } | ||
| if (coerce && typeof obj == 'boolean') { | ||
| return `${Number(obj)}`; | ||
| } | ||
| return obj; | ||
| }; | ||
| /** | ||
| * Calculates object hash | ||
| * @param {Object} obj Object to hash | ||
| * @param {Object} [options] Options | ||
| * @param {string} [options.alg="sha256"] Crypto algorithm to use | ||
| * @param {string} [options.enc="hex"] Hash string encoding | ||
| * @param {boolean} [options.coerce=true] Perform coercion | ||
| * @param {boolean} [options.sort=true] Perform Array, Set, Object sorting | ||
| * @returns {string} Hash string | ||
| */ | ||
| exports.hash = (obj, options) => { | ||
| options = options || {}; | ||
| const alg = options.alg || 'sha256'; | ||
| const enc = options.enc || 'hex'; | ||
| const coerce = typeof options.coerce == 'undefined' ? true : options.coerce; | ||
| const sort = typeof options.sort == 'undefined' ? true : options.sort; | ||
| if (~crypto.getHashes().indexOf(alg)) { | ||
| const sorted = exports.sortedObjectString(obj, {coerce, sort}); | ||
| return crypto.createHash(alg) | ||
| .update(sorted) | ||
| .digest(enc); | ||
| } else { | ||
| throw new Error(`Algorithm ${alg} not supported by "ctypto" module`); | ||
| } | ||
| }; |
-27
| /** | ||
| * Created on 8/17/16. | ||
| */ | ||
| 'use strict'; | ||
| const hash = require('../index').hash; | ||
| const assert = require('chai').assert; | ||
| const obj1 = { | ||
| a: 1, | ||
| c: 2, | ||
| b: "3" | ||
| }; | ||
| const obj2 = { | ||
| c: 2, | ||
| b: "3", | ||
| a: 1 | ||
| }; | ||
| describe(`node-object-hash`, () => { | ||
| describe(`#hash(obj, {coerce: true, sort: true})`, () => { | ||
| it(`should return equal hashes`, () => { | ||
| assert.equal(hash(obj1), hash(obj2)); | ||
| }); | ||
| }); | ||
| }); |
| /** | ||
| * Created on 7/5/16. | ||
| */ | ||
| 'use strict'; | ||
| const sortedObjectString = require('../index').sortedObjectString; | ||
| const assert = require('chai').assert; | ||
| const object = { | ||
| x: new Map([['a', 1], ['c', 3], ['b', 2]]), | ||
| z: new Set([4,3,2,1]), | ||
| f: 2, | ||
| e: [{a:2}, 3, {g:2, e:{d:1}}, "rt", "1", "2abc", "3d"], | ||
| d: [3,5,6,1,2,3], | ||
| c: "3ab", | ||
| b: "2", | ||
| a: 1 | ||
| }; | ||
| describe(`node-object-hash`, () => { | ||
| describe(`#sortedObjectString(obj, {coerce: true, sort: true})`, () => { | ||
| it(`should return properly sorted object string`, () => { | ||
| let options = {coerce: true, sort: true}; | ||
| assert.equal(sortedObjectString(object, options), `{a,1,b,2,c,3ab,d,[1,2,3,3,5,6],e,[1,2abc,3,3d,rt,{a,2},{e,{d,1},g,2}],f,2,x,Map[a,1,c,3,b,2],z,Set[1,2,3,4]}`); | ||
| }); | ||
| }); | ||
| }); | ||
| describe(`node-object-hash`, () => { | ||
| describe(`#sortedObjectString(obj, {coerce: true, sort: false})`, () => { | ||
| it(`should return properly sorted object string`, () => { | ||
| let options = {coerce: true, sort: false}; | ||
| assert.equal(sortedObjectString(object, options), `{x,Map[a,1,c,3,b,2],z,Set[4,3,2,1],f,2,e,[{a,2},3,{g,2,e,{d,1}},rt,1,2abc,3d],d,[3,5,6,1,2,3],c,3ab,b,2,a,1}`); | ||
| }); | ||
| }); | ||
| }); | ||
| describe(`node-object-hash`, () => { | ||
| describe(`#sortedObjectString(obj, {coerce: false, sort: true})`, () => { | ||
| it(`should return properly sorted object string`, () => { | ||
| let options = {coerce: false, sort: true}; | ||
| assert.equal(sortedObjectString(object, options), `{a,1,b,"2",c,"3ab",d,[1,2,3,3,5,6],e,["1","2abc","3d","rt",3,{a,2},{e,{d,1},g,2}],f,2,x,Map[a,1,c,3,b,2],z,Set[1,2,3,4]}`); | ||
| }); | ||
| }); | ||
| }); | ||
| describe(`node-object-hash`, () => { | ||
| describe(`#sortedObjectString(obj, {coerce: false, sort: false})`, () => { | ||
| it(`should return properly sorted object string`, () => { | ||
| let options = {coerce: false, sort: false}; | ||
| assert.equal(sortedObjectString(object, options), `{x,Map[a,1,c,3,b,2],z,Set[4,3,2,1],f,2,e,[{a,2},3,{g,2,e,{d,1}},"rt","1","2abc","3d"],d,[3,5,6,1,2,3],c,"3ab",b,"2",a,1}`); | ||
| }); | ||
| }); | ||
| }); | ||
| describe(`node-object-hash`, () => { | ||
| describe(`#sortedObjectString(obj, {coerce: false, sort: false}) Array`, () => { | ||
| it(`should return properly sorted object string`, () => { | ||
| const src = [5,'4',3,'1',2]; | ||
| let options = {coerce: true, sort: true}; | ||
| assert.equal(sortedObjectString(src, options), `[1,2,3,4,5]`); | ||
| }); | ||
| }); | ||
| }); | ||
| describe(`node-object-hash`, () => { | ||
| describe(`#sortedObjectString(obj, {coerce: false, sort: false}) Object`, () => { | ||
| it(`should return properly sorted object string`, () => { | ||
| const src = {'b': 1, a:2, c:'5', d:1}; | ||
| let options = {coerce: true, sort: true}; | ||
| assert.equal(sortedObjectString(src, options), `{a,2,b,1,c,5,d,1}`); | ||
| }); | ||
| }); | ||
| }); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
18207
107.18%8
33.33%466
169.36%1
-50%109
53.52%4
100%1
Infinity%