TL; DR版本:我想避免将重复的Javascript对象添加到类似对象的数组中,其中一些可能非常大。什么是最好的方法?
我有一个应用程序,我将大量JSON数据加载到Javascript数据结构中。虽然它比这复杂一点,但假设我通过一系列AJAX请求将JSON加载到服务器的Javascript对象数组中,例如:
var myObjects = [];
function processObject(o) {
myObjects.push(o);
}
for (var x=0; x<1000; x++) {
$.getJSON('/new_object.json', processObject);
}
使问题复杂化,JSON:
我最初的想法是有一个额外的对象来存储每个对象的哈希值(通过JSON.stringify
?)并在每次加载时检查它,如下所示:
var myHashMap = {};
function processObject(o) {
var hash = JSON.stringify(o);
// is it in the hashmap?
if (!(myHashMap[hash])) {
myObjects.push(o);
// set the hashmap key for future checks
myHashMap[hash] = true;
}
// else ignore this object
}
但我担心myHashMap
中的属性名称长度可能为200 kb。所以我的问题是:
JSON.stringify
的任意长度和架构的JSON对象创建哈希函数?答案 0 :(得分:4)
我建议您创建JSON.stringify(o)的MD5哈希值,并将其存储在您的hashmap中,并将对存储对象的引用存储为哈希值。并且为了确保JSON.stringify()
中没有对象键顺序差异,您必须创建对键进行排序的对象的副本。
然后,当每个新对象进入时,您将根据哈希映射进行检查。如果在哈希映射中找到匹配项,则将传入对象与您存储的实际对象进行比较,以查看它们是否真正重复(因为可能存在MD5哈希冲突)。这样,您就拥有了一个可管理的哈希表(其中只有MD5哈希值)。
这是用于创建对象的规范字符串表示的代码(包括嵌套对象或数组中的对象),如果您刚刚调用JSON.stringify(),则处理可能处于不同顺序的对象键。
// Code to do a canonical JSON.stringify() that puts object properties
// in a consistent order
// Does not allow circular references (child containing reference to parent)
JSON.stringifyCanonical = function(obj) {
// compatible with either browser or node.js
var Set = typeof window === "object" ? window.Set : global.Set;
// poor man's Set polyfill
if (typeof Set !== "function") {
Set = function(s) {
if (s) {
this.data = s.data.slice();
} else {
this.data = [];
}
};
Set.prototype = {
add: function(item) {
this.data.push(item);
},
has: function(item) {
return this.data.indexOf(item) !== -1;
}
};
}
function orderKeys(obj, parents) {
if (typeof obj !== "object") {
throw new Error("orderKeys() expects object type");
}
var set = new Set(parents);
if (set.has(obj)) {
throw new Error("circular object in stringifyCanonical()");
}
set.add(obj);
var tempObj, item, i;
if (Array.isArray(obj)) {
// no need to re-order an array
// but need to check it for embedded objects that need to be ordered
tempObj = [];
for (i = 0; i < obj.length; i++) {
item = obj[i];
if (typeof item === "object") {
tempObj[i] = orderKeys(item, set);
} else {
tempObj[i] = item;
}
}
} else {
tempObj = {};
// get keys, sort them and build new object
Object.keys(obj).sort().forEach(function(item) {
if (typeof obj[item] === "object") {
tempObj[item] = orderKeys(obj[item], set);
} else {
tempObj[item] = obj[item];
}
});
}
return tempObj;
}
return JSON.stringify(orderKeys(obj));
}
并且算法
var myHashMap = {};
function processObject(o) {
var stringifiedCandidate = JSON.stringifyCanonical(o);
var hash = CreateMD5(stringifiedCandidate);
var list = [], found = false;
// is it in the hashmap?
if (!myHashMap[hash] {
// not in the hash table, so it's a unique object
myObjects.push(o);
list.push(myObjects.length - 1); // put a reference to the object with this hash value in the list
myHashMap[hash] = list; // store the list in the hash table for future comparisons
} else {
// the hash does exist in the hash table, check for an exact object match to see if it's really a duplicate
list = myHashMap[hash]; // get the list of other object indexes with this hash value
// loop through the list
for (var i = 0; i < list.length; i++) {
if (stringifiedCandidate === JSON.stringifyCanonical(myObjects[list[i]])) {
found = true; // found an exact object match
break;
}
}
// if not found, it's not an exact duplicate, even though there was a hash match
if (!found) {
myObjects.push(o);
myHashMap[hash].push(myObjects.length - 1);
}
}
}
jsonStringifyCanonical()
的测试用例位于:https://jsfiddle.net/jfriend00/zfrtpqcL/
答案 1 :(得分:2)
JSON.stringify(o)
保证相同的密钥排序。因为{foo: 1, bar: 2}
和{bar: 2, foo: 1}
等于对象,而不是字符串。一种可能的优化:
而不是使用getJSON
使用$.get
并将"text"
作为dataType
param传递。比你可以使用结果作为你的哈希并在之后转换为对象。
实际上,通过写下最后一句话,我会谈到另一个解决方案:
$.get
将所有结果收集到数组Array.sort
for
同样,不同的JSON字符串可以构成相同的JavaScript对象。