json-diff-rfc6902
Version:
This framework is to compare two JSON data and generate the Patch
884 lines (747 loc) • 26.3 kB
JavaScript
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.jdr = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var applyPatches = require('./lib/applyPatches');
var unchangedArea = require('./lib/unchangedArea.js');
var patchArea = require('./lib/patchArea.js');
var hashObject = require('./lib/hashObject.js');
exports.diff = diff;
exports.apply = apply;
// browserify -s jdr -e JSON-Diff.js -o json-diff-rfc6902.js
var OBJ_COM = true;
var ARR_COM = true;
var HASH_ID = null;
function apply(app_old, jpn_patch) {
applyPatches.apply(app_old, jpn_patch);
}
function diff(oldJson, newJson, options) {
// Initial
if(typeof options === 'object') {
if(options.OBJ_COM !== void 0) {OBJ_COM = options.OBJ_COM;}
if(options.ARR_COM !== void 0) {ARR_COM = options.ARR_COM;}
if(options.HASH_ID !== void 0) {HASH_ID = options.HASH_ID;}
}
// Get the unchanged area
var unchanged = [];
if (OBJ_COM === true) {
unchangedArea.generateUnchanged(oldJson, newJson, unchanged, '');
}
// Generate the diff
var patches = [];
generateDiff(oldJson, newJson, unchanged, patches, '');
patchArea.handlePatch(patches);
return patches;
}
function generateDiff(oldJson, newJson, unchanged, patches, path) {
// var a = null object Array.isArray: false
// var a = 5 number
// var a = [1,2] object Array.isArray: true
// var a undefined Array.isArray: false
if (Array.isArray(oldJson) && Array.isArray(newJson)) {
generateArrayDiff(oldJson, newJson, unchanged, patches, path);
return;
}
if (typeof oldJson === "object" && oldJson !== null && typeof newJson === "object" && newJson !== null) {
generateObjectDiff(oldJson, newJson, unchanged, patches, path);
return;
}
return generateValueDiff(oldJson, newJson, unchanged, patches, path);
}
function generateValueDiff(oldJson, newJson, unchanged, patches, path) {
// the endpoint
if (newJson !== oldJson) {
patches.push({ op: "replace", path: path, value: newJson});
}
}
function generateArrayDiff(oldJson, newJson, unchanged, patches, path) {
// console.log("--------This is Array-------------");
// x, y is the hash of json
// var x = hashObject.map(hashObject.hash, oldJson);
// var y = hashObject.map(hashObject.hash, newJson);
if (oldJson.length === 0 && newJson.length ===0 ) {return;}
// Use LCS
var tmpPatches = [];
var tmpPatchHashes = [];
if (oldJson.length === 0) {
patches.push({ op: "add", path: path, value: newJson});
} else {
// Use sortBack
tmpPatches = transformArray(oldJson, newJson, unchanged, tmpPatches, tmpPatchHashes, path);
for (var l = 0; l < tmpPatches.length; l++) {
patches.push(tmpPatches[l]);
}
}
}
function generateObjectDiff(oldJson, newJson, unchanged, patches, path) {
var oldKeys = Object.keys(oldJson);
var newKeys = Object.keys(newJson);
var removed = false;
var oldKey, oldValue;
// Loop from the old; from lengths -1 to 0
for (var i = oldKeys.length -1; i >= 0; i--) {
oldKey = oldKeys[i];
oldValue = oldJson[oldKey];
if (newJson.hasOwnProperty(oldKey)) {
// go deeper
generateDiff(oldJson[oldKey], newJson[oldKey], unchanged, patches, path + "/" + oldKey);
} else {
// Remove
removed = true;
patches.push({ op: "remove", path: path + "/" + patchPointString(oldKey), value: oldValue });
}
}
// If doesn't remove and the length is the same, return
// Return: only the length is equal and doesn't remove
if (!removed && newKeys.length === oldKeys.length) { return; }
// Loop from the new
// length is not the same
var newKey;
var newVal;
for (var j = 0; j < newKeys.length; j ++) {
newKey = newKeys[j];
newVal = newJson[newKey];
if (!oldJson.hasOwnProperty(newKey)) {
//Try to find the value in the unchanged area
// change JSON.stringify()
var pointer = unchangedArea.findValueInUnchanged(JSON.stringify(newVal), unchanged);
if (pointer) {
//COPY
patches.push({ op: "copy", path: path + "/" + patchPointString(newKey), from: pointer});
} else {
// no json.stringnify
var previousIndex = -1;
if (OBJ_COM === true) {
previousIndex = patchArea.findValueInPatch(newVal, patches);
}
if (previousIndex !== -1 && patches[previousIndex].op === 'remove') {
// MOVE
var oldPath = patches[previousIndex].path;
patches.splice(previousIndex, 1);
patches.push({ op: "move", from: oldPath, path: path + "/" + patchPointString(newKey)});
} else {
//ADD
patches.push({ op: "add", path: path + "/" + patchPointString(newKey), value: newVal});
}
}
}
}
}
function patchPointString(str) {
// According to RFC 6901
// '~' needs to be encoded as '~0'
// '/' needs to be encoded as '~1'
if (str.indexOf('/') === -1 && str.indexOf('~') === -1) {
return str;
}
return str.replace(/~/g, '~0').replace(/\//g, '~1');
}
function transformIndex(element, m, array) {
var finalIndex;
switch(element.op) {
case 'add':
case 'replace':
case 'copy':
// When add, replace and copy, add directly
return element.index;
case 'remove':
finalIndex = element.index;
break;
case 'move':
finalIndex = element.from;
break;
}
for (var i = 0; i < m; i++) {
switch (array[i].op) {
case 'remove':
if(finalIndex > array[i].index) {
// when equal, don't --
finalIndex --;
}
break;
case 'add':
case 'copy':
if(finalIndex >= array[i].index) {
// when equal, do ++
finalIndex ++;
}
break;
case 'replace':
// when equal, don't change
break;
case 'move':
if (array[i].from !== array[i].index) {
var min = Math.min(array[i].from, array[i].index);
var max = Math.max(array[i].from, array[i].index);
if (finalIndex >= min && finalIndex <= max) {
if (array[i].from > array[i].index) {
finalIndex ++;
} else {
finalIndex --;
}
}
}
break;
}
}
return finalIndex;
}
function operationValue (op) {
switch (op) {
case "move" : return 0;
case "remove" : return 1;
case "add" : return 2;
case "replace": return 3;
case "copy" : return 4;
}
}
function compare(a, b) {
if (a.index === b.index) {
// Order: move < remove < add
var a_value = operationValue(a.op);
var b_value = operationValue(b.op);
if (a_value > b_value) {
return 1;
} else {
return -1;
}
} else {
return a.index - b.index;
}
}
function findCopyInArray(element, m, array, arrUnchanged) {
var copyIndex = -1;
for (var i = 0; i < m; i++) {
switch (element.op) {
case 'remove':
case 'copy':
break;
default:
// Move Replace Add
if (element.hash === array[i].hash) {
return array[i].index;
}
break;
}
}
//Find value in arrUnchanged
for (var j= 0; j< arrUnchanged.length; j++) {
if (element.hash === arrUnchanged[j].hash) {
return arrUnchanged[j].index;
}
}
return copyIndex;
}
function transformArray(oldJson, newJson, unchanged, patches, patchHashes, path, jsondiff) {
//When is the Array, stop to find leaf node
// (hash, value, index)
var x = hashObject.mapArray(hashObject.hash, oldJson, HASH_ID);
var y = hashObject.mapArray(hashObject.hash, newJson, HASH_ID);
// Reserve the origin index
// COPY ARRAY
var x_sorted = x.slice();
var y_sorted = y.slice();
x_sorted.sort(function(a, b) {return a.hash - b.hash;});
y_sorted.sort(function(a, b) {return a.hash - b.hash;});
//Diff
var arrPatch = [], arrUnchanged = [], arrtmp = [];
var i= 0, j = 0;
while (i < x_sorted.length) {
while( j < y_sorted.length) {
if(x_sorted[i] !== void 0) {
if (x_sorted[i].hash > y_sorted[j].hash) {
arrPatch.push({op: "add", value: y_sorted[j].value, index: y_sorted[j].index, hash: y_sorted[j].hash });
j++;
} else if (x_sorted[i].hash === y_sorted[j].hash) {
// Unchanged push
unchanged.push( path + '/' + y_sorted[j].index + "=" + JSON.stringify(x_sorted[i].hash));
arrPatch.push({op: "move", value: y_sorted[j].value, valueOld: x_sorted[i].value, from: x_sorted[i].index , index: y_sorted[j].index, hash: y_sorted[j].hash });
i++;
j++;
} else {
arrPatch.push({op: "remove", index: x_sorted[i].index, value: x_sorted[i].value});
i++;
}
} else {
arrPatch.push({op: "add", value: y_sorted[j].value, index: y_sorted[j].index, hash: y_sorted[j].hash });
j++;
}
}
if (i < x_sorted.length) {
// Remove the rest elements of the x_sorted
arrPatch.push({op: "remove", index: x_sorted[i].index, value: x_sorted[i].value });
i++;
}
}
//Get the patch to make all the elements are the same, but index is random
arrPatch = arrPatch.sort(compare);
var m = 0;
while(arrPatch[m] !== void 0) {
// f_index = transformIndex(arrPatch[m], arrPatch);
switch(arrPatch[m].op) {
case 'add':
arrPatch[m].index = transformIndex(arrPatch[m], m, arrPatch);
// replace
if (arrPatch[m-1] !== void 0 ) {
if (arrPatch[m-1].op === 'remove' && arrPatch[m-1].index === arrPatch[m].index) {
// if replace a object, go deeper
// Set thresholds length == 30
// if (JSON.stringify(arrPatch[m-1].value).length > 20 && typeof arrPatch[m-1].value === "object" && arrPatch[m-1].value !== null && typeof arrPatch[m].value === "object" && arrPatch[m].value !== null) {
if (typeof arrPatch[m-1].value === "object" && arrPatch[m-1].value !== null && typeof arrPatch[m].value === "object" && arrPatch[m].value !== null) {
if (ARR_COM === true) {
var tmPatch = [];
//1.
generateDiff(arrPatch[m-1].value, arrPatch[m].value, unchanged, tmPatch, path + "/" + arrPatch[m-1].index);
//2.
// tmPatch = fjp.compare(arrPatch[m-1].value, arrPatch[m].value);
//Need to be fixed.
arrPatch[m].op = 'replace';
arrPatch.splice(m-1,1);
arrtmp.pop();
arrtmp = arrtmp.concat(tmPatch);
continue;
} else {
arrPatch[m].op = 'replace';
arrPatch.splice(m-1,1);
arrtmp.pop();
arrtmp.push({op: "replace", value: arrPatch[m-1].value, path: path + '/' + arrPatch[m-1].index});
continue;
}
} else {
arrPatch[m].op = 'replace';
arrPatch.splice(m-1,1);
arrtmp.pop();
arrtmp.push({op: "replace", value: arrPatch[m-1].value, path: path + '/' + arrPatch[m-1].index});
continue;
}
}
}
// COPY
var copyIndex = findCopyInArray(arrPatch[m], m, arrPatch, arrUnchanged);
if (copyIndex !== -1) {
arrPatch[m].op = 'copy';
arrPatch[m].from = copyIndex;
if (arrPatch[m].index === arrPatch[m].from) {
arrPatch.splice(m, 1);
continue;
}
arrtmp.push({op: "copy", from: path + '/' + arrPatch[m].from, path: path + '/' + arrPatch[m].index});
} else {
arrtmp.push({op: "add", value: arrPatch[m].value , path: path + '/' + arrPatch[m].index});
}
break;
case 'remove':
arrPatch[m].index = transformIndex(arrPatch[m], m, arrPatch);
if (arrPatch[m-1] !== void 0) {
// change move 2->1 and remove 2 to remove 1
if (arrPatch[m-1].op === 'move' && arrPatch[m-1].from === arrPatch[m].index && arrPatch[m-1].from === (arrPatch[m-1].index + 1) ) {
arrPatch[m].index = arrPatch[m-1].index;
arrPatch.splice(m-1,1);
arrtmp.pop();
arrtmp.push({op: "remove", path: path + '/' + arrPatch[m-1].index});
continue;
} else {
arrtmp.push({op: "remove", path: path + '/' + arrPatch[m].index});
}
} else {
arrtmp.push({op: "remove", path: path + '/' + arrPatch[m].index});
}
break;
case 'move':
arrPatch[m].from = transformIndex(arrPatch[m], m, arrPatch);
if (arrPatch[m].index === arrPatch[m].from) {
if (JSON.stringify(arrPatch[m].valueOld) === JSON.stringify(arrPatch[m].value)) {
arrUnchanged.push(arrPatch[m]);
arrPatch.splice(m, 1);
continue;
} else {
//If index is the same, go to the internal node
var tmMove = [];
generateDiff(arrPatch[m].valueOld, arrPatch[m].value, unchanged, tmMove, path + "/" + arrPatch[m].index);
// Remove current move in the patch.
arrPatch.splice(m,1);
arrtmp = arrtmp.concat(tmMove);
continue;
}
}
arrtmp.push({op: "move", from: path + '/' + arrPatch[m].from, path: path + '/' + arrPatch[m].index});
break;
}
m++;
}
arrPatch = arrPatch.map(function(obj) {
obj.path = path + '/' + obj.index;
delete obj.hash;
delete obj.index;
if (obj.op === 'move' || obj.op === 'copy') {
obj.from = path + '/' + obj.from;
delete obj.value;
}
return obj;
});
return arrtmp;
}
},{"./lib/applyPatches":2,"./lib/hashObject.js":4,"./lib/patchArea.js":5,"./lib/unchangedArea.js":6}],2:[function(require,module,exports){
exports.apply = apply;
var objectOps = {
add: function(child_json, key, all_json) {
// console.log(this);
// console.log(child_json);
child_json[key] = this.value;
// console.log("Add operation = " + this.value);
return true;
},
remove: function(child_json, key, all_json) {
delete child_json[key];
// console.log("Remove operation = " + child_json);
return true;
},
replace: function(child_json, key, all_json) {
child_json[key] = this.value;
// console.log("replace operation = " + this.value);
return true;
},
copy: function(child_json, key, all_json) {
// console.log("copy operation = " + JSON.stringify(child_json));
// console.log("key = " + key);
var tmpOp = {"op": "val_get", "path": this.from};
//Get the tmp value
apply(all_json, [tmpOp]);
apply(all_json, [{
"op": "add", "path": this.path, "value": tmpOp.value
}]);
return true;
},
move: function(child_json, key, all_json) {
// console.log("move operation = " + JSON.stringify(child_json));
var tmpOp = {"op": "val_get", "path": this.from};
//Get the tmp value
apply(all_json, [tmpOp]);
apply(all_json, [{"op": "remove", "path": this.from}]);
apply(all_json, [{"op": "add", "path": this.path, "value": tmpOp.value}]);
return true;
},
val_get: function(child_json, key) {
this.value = child_json[key];
}
};
var arrayOps = {
add: function(arr, key, all_json) {
arr.splice(key, 0, this.value);
// console.log("Add operation = " + this.value);
return true;
},
remove: function(arr, key, all_json) {
arr.splice(key, 1);
// console.log("Remove operation = " + key);
return true;
},
replace: function(arr, key, all_json) {
arr[key] = this.value;
return true;
},
copy: objectOps.copy,
move: objectOps.move,
val_get: objectOps.val_get
};
var rootOps = {
remove: function(root_json) {
for (var element in root_json) {
// this here is the patch
objectOps.remove.call(this, root_json, element);
}
return true;
},
add: function(root_json) {
rootOps.remove.call(this, root_json);
for (var element in this.value) {
root_json[element] = this.value[element];
}
return true;
},
replace: function(root_json) {
apply(root_json, [{"op": "remove", "path": this.path}]);
apply(root_json, [{"op": "add", "path": this.path, "value": this.value}]);
return true;
},
copy: objectOps.copy,
move: objectOps.move,
val_get: function(child_json) {
this.value = child_json;
}
};
function apply(all_json, patches) {
for (var i = 0; i < patches.length; i++) {
var patch = patches[i];
if (patch !== void 0) {
//when patch = "", it's the root
var path = patch.path || "";
// console.log(path);
var keys = path.split("/");
var child_json = all_json;
//first element is undefined
var key;
//child_json is the second end element
for (var j = 1; j < keys.length - 1; j++) {
key = keys[j];
child_json = child_json[key];
}
//key is the last element's path
key = keys[keys.length -1];
//This is the root operations
// Attention: "" is not undefined
if (key === undefined) {
// console.log("The key is undefined");
rootOps[patch.op].call(patch, child_json, stringToPoint(key), all_json);
break;
}
if (Array.isArray(child_json)) {
// console.log("***********Array operations****************");
if (key === '-') {
key = child_json.length;
} else {
key = parseInt(key);
}
arrayOps[patch.op].call(patch, child_json, key, all_json);
} else {
// console.log("***********Object operations***************");
objectOps[patch.op].call(patch, child_json, stringToPoint(key), all_json);
}
}
}
}
function stringToPoint(str) {
// According to RFC 6901
// '~' needs to be encoded as '~0'
// '/' needs to be encoded as '~1'
if(str && str.indexOf('~') !== -1) {
return str.replace(/~0/g, '~').replace(/~1/g, '/');
}
return str;
}
},{}],3:[function(require,module,exports){
module.exports._equals = _equals;
/**
* Compare 2 JSON values, or recursively compare 2 JSON objects or arrays
* @param {object|array|string|number|boolean|null} a
* @param {object|array|string|number|boolean|null} b
* @returns {boolean} true iff a and b are recursively equal
*/
var _objectKeys = (function () {
if (Object.keys)
{return Object.keys;}
return function (o) {
var keys = [];
for (var i in o) {
if (o.hasOwnProperty(i)) {
keys.push(i);
}
}
return keys;
};
})();
var _isArray;
if (Array.isArray) {
_isArray = Array.isArray;
} else {
_isArray = function (obj) {
return obj.push && typeof obj.length === 'number';
};
}
/**
* _equals - This can save a lot of time 5 ms
*
* @param {type} a description
* @param {type} b description
* @return {type} description
*/
function _equals(a, b) {
switch (typeof a) {
case 'undefined':
case 'boolean':
case 'string':
case 'number':
return a === b;
case 'object':
if (a === null)
{return b === null;}
if (_isArray(a)) {
if (!_isArray(b) || a.length !== b.length)
{return false;}
for (var i = 0, l = a.length; i < l; i++) {
if (!_equals(a[i], b[i]))
{return false;}
}
return true;
}
var bKeys = _objectKeys(b);
var bLength = bKeys.length;
if (_objectKeys(a).length !== bLength)
{return false;}
for (var i = 0, k; i < bLength; i++) {
k = bKeys[i];
if (!(k in a && _equals(a[k], b[k])))
{return false;}
}
return true;
default:
return false;
}
}
},{}],4:[function(require,module,exports){
var hashToNum = require('string-hash');
exports.hash = hash;
exports.map = map;
exports.mapArray = mapArray;
function hash(obj, HASH_ID) {
//Default hash
// return JSON.stringify(obj);
// return id|id_str|title || obj.title
if (obj[HASH_ID] !== void 0 ) {
return typeof obj[HASH_ID] === "string"? hashToNum(obj[HASH_ID]): obj[HASH_ID];
} else {
// || hashToNum(JSON.stringify(obj))
// || (obj.title === undefined)? obj.title: hashToNum(JSON.stringify(obj.title))
return obj.id || obj._id || (obj.title === undefined? obj.title: hashToNum(JSON.stringify(obj.title))) || hashToNum(JSON.stringify(obj));
}
}
/**
* map the array. Faster than Array.prototype.map
*
* @param {function} f function
* @param {Array} a array-like
* @return {Array} new Array mapped by f
*/
function map(f, a) {
var b = new Array({});
for (var i = 0; i < a.length; i++) {
b[i] = f(typeof a[i] === "string"? a[i]: JSON.stringify(a[i]));
// JSON.stringnify
// b[i] = f(a[i]);
}
return b;
}
/**
* map the array. Faster than Array.prototype.map
*
* @param {function} f function
* @param {Array} a array-like
* @return {Array} new Array mapped by f
*/
function mapArray(f, a, HASH_ID) {
var b = [];
for (var i = 0; i < a.length; i++) {
b[i] = {};
// b[i].hash = f(typeof a[i] === "string"? a[i]: JSON.stringify(a[i]));
b[i].hash = f(a[i], HASH_ID);
b[i].index = i;
b[i].value = a[i];
}
return b;
}
},{"string-hash":7}],5:[function(require,module,exports){
exports.findValueInPatchHashes = findValueInPatchHashes;
exports.findValueInPatch = findValueInPatch;
exports.handlePatch = handlePatch;
function findValueInPatchHashes(newValue, patchHashes) {
var patchValue;
for (var i = 0; i < patchHashes.length; i++) {
patchValue = patchHashes[i].value;
if (newValue === patchValue) {
return i;
}
}
return -1;
}
function findValueInPatch(newValue, patches) {
var patchValue;
for (var i = 0; i < patches.length; i++) {
patchValue = patches[i].value;
if (newValue === patchValue) {
return i;
}
}
return -1;
}
function handlePatch(patches) {
// Delete the value in 'remove' option
for (var i = 0; i < patches.length; i++) {
// patches[i] = JSON.parse(patches[i]);
if (patches[i].op === 'remove') {
delete patches[i].value;
}
}
}
},{}],6:[function(require,module,exports){
var deepEqual = require('./deepEquals.js');
var hashObject = require('./hashObject.js');
var applyPatches = require('./applyPatches');
exports.generateUnchanged = generateUnchanged;
exports.findValueInUnchanged = findValueInUnchanged;
function generateUnchanged(oldJson, newJson, unchanged, path) {
// Check if two json is the same
// Equal
if (deepEqual._equals(oldJson, newJson)) {
// if (equal(oldJson, newJson)) {
// console.log({path: path, value: copy.clone(newJson)});
unchanged.push( path + "=" + JSON.stringify(newJson));
return;
}
// Not equal
// Check the type
if (typeof oldJson !== typeof newJson) { return; }
// Type is the same
if (Array.isArray(oldJson) && Array.isArray(newJson)) {
// Array
generateUnchangedArray(oldJson, newJson, unchanged, path);
return;
}
if (typeof oldJson === "object" && oldJson !== null && typeof newJson === "object" && newJson !== null) {
// Object
generateUnchangedObject(oldJson, newJson, unchanged, path);
return;
}
}
function arrayCompare(oldArr, newArr, unchanged, path) {
// Check if two array element (string) is the same
// Equal
if (oldArr === newArr) {
// if (equal(oldJson, newJson)) {
// console.log({path: path, value: copy.clone(newJson)});
unchanged.push( path + "=" + newArr);
return;
}
}
//********************Need to be changed ********************
function generateUnchangedArray(oldJson, newJson, unchanged, path) {
// Do nothing now
// Generate when diff
}
function generateUnchangedObject(oldJson, newJson, unchanged, path) {
var oldKeys = Object.keys(oldJson);
var newKeys = Object.keys(newJson);
for (var i = 0; i < oldKeys.length; i++) {
var oldKey = oldKeys[i];
if (newJson.hasOwnProperty(oldKey)) {
generateUnchanged(oldJson[oldKey], newJson[oldKey], unchanged, path + "/" + oldKey);
}
}
}
function findValueInUnchanged(newValue, unchanged) {
for (var i = 0; i < unchanged.length; i++) {
var value = unchanged[i].split("=")[1];
if (newValue.toString() === value) {
return unchanged[i].split("=")[0];
}
}
}
},{"./applyPatches":2,"./deepEquals.js":3,"./hashObject.js":4}],7:[function(require,module,exports){
module.exports = function(str) {
var hash = 5381,
i = str.length
while(i)
hash = (hash * 33) ^ str.charCodeAt(--i)
/* JavaScript does bitwise operations (like XOR, above) on 32-bit signed
* integers. Since we want the results to be always positive, if the high bit
* is set, unset it and add it back in through (64-bit IEEE) addition. */
return hash >= 0 ? hash : (hash & 0x7FFFFFFF) + 0x80000000
}
},{}]},{},[1])(1)
});