Node.js: How to serialize a large object with circular references - javascript

I use Node.js and want to serialize a large javascript object to HDD. The object is basically a "hashmap" and only contains data, not functions. The object contains elements with circular references.
This is an online application so the process should not block the main loop. In my use-case Non-blocking is much more important than speed (data is live in-memory data and is only load at startup, saves are for timed backups every X minutes and at shutdown/failure)
What is the best way to do this? Pointers to libraries that do what I want are more than welcome.

I have a nice solution I've been using. Its downside is that it has an O(n^2) runtime which makes me sad.
Here's the code:
// I defined these functions as part of a utility library called "U".
var U = {
isObj: function(obj, cls) {
try { return obj.constructor === cls; } catch(e) { return false; };
},
straighten: function(item) {
/*
Un-circularizes data. Works if `item` is a simple Object, an Array, or any inline value (string, int, null, etc).
*/
var arr = [];
U.straighten0(item, arr);
return arr.map(function(item) { return item.calc; });
},
straighten0: function(item, items) {
/*
The "meat" of the un-circularization process. Returns the index of `item`
within the array `items`. If `item` didn't initially exist within
`items`, it will by the end of this function, therefore this function
always produces a usable index.
Also, `item` is guaranteed to have no more circular references (it will
be in a new format) once its index is obtained.
*/
/*
STEP 1) If `item` is already in `items`, simply return it.
Note that an object's existence can only be confirmed by comparison to
itself, not an un-circularized version of itself. For this reason an
`orig` value is kept ahold of to make such comparisons possible. This
entails that every entry in `items` has both an `orig` value (the
original object, for comparison) and a `calc` value (the calculated, un
circularized value).
*/
for (var i = 0, len = items.length; i < len; i++) // This is O(n^2) :(
if (items[i].orig === item) return i;
var ind = items.length;
// STEP 2) Depending on the type of `item`, un-circularize it differently
if (U.isObj(item, Object)) {
/*
STEP 2.1) `item` is an `Object`. Create an un-circularized version of
that `Object` - keep all its keys, but replace each value with an index
that points to that values.
*/
var obj = {};
items.push({ orig: item, calc: obj }); // Note both `orig` AND `calc`.
for (var k in item)
obj[k] = U.straighten0(item[k], items);
} else if (U.isObj(item, Array)) {
/*
STEP 2.2) `item` is an `Array`. Create an un-circularized version of
that `Array` - replace each of its values with an index that indexes
the original value.
*/
var arr = [];
items.push({ orig: item, calc: arr }); // Note both `orig` AND `calc`.
for (var i = 0; i < item.length; i++)
arr.push(U.straighten0(item[i], items));
} else {
/*
STEP 2.3) `item` is a simple inline value. We don't need to make any
modifications to it, as inline values have no references (let alone
circular references).
*/
items.push({ orig: item, calc: item });
}
return ind;
},
unstraighten: function(items) {
/*
Re-circularizes un-circularized data! Used for undoing the effects of
`U.straighten`. This process will use a particular marker (`unbuilt`) to
show values that haven't yet been calculated. This is better than using
`null`, because that would break in the case that the literal value is
`null`.
*/
var unbuilt = { UNBUILT: true };
var initialArr = [];
// Fill `initialArr` with `unbuilt` references
for (var i = 0; i < items.length; i++) initialArr.push(unbuilt);
return U.unstraighten0(items, 0, initialArr, unbuilt);
},
unstraighten0: function(items, ind, built, unbuilt) {
/*
The "meat" of the re-circularization process. Returns an Object, Array,
or inline value. The return value may contain circular references.
*/
if (built[ind] !== unbuilt) return built[ind];
var item = items[ind];
var value = null;
/*
Similar to `straighten`, check the type. Handle Object, Array, and inline
values separately.
*/
if (U.isObj(item, Object)) {
// value is an ordinary object
var obj = built[ind] = {};
for (var k in item)
obj[k] = U.unstraighten0(items, item[k], built, unbuilt);
return obj;
} else if (U.isObj(item, Array)) {
// value is an array
var arr = built[ind] = [];
for (var i = 0; i < item.length; i++)
arr.push(U.unstraighten0(items, item[i], built, unbuilt));
return arr;
}
built[ind] = item;
return item;
},
thingToString: function(thing) {
/*
Elegant convenience function to convert any structure (circular or not)
to a string! Now that this function is available, you can ignore
`straighten` and `unstraighten`, and the headaches they may cause.
*/
var st = U.straighten(thing);
return JSON.stringify(st);
},
stringToThing: function(string) {
/*
Elegant convenience function to reverse the effect of `U.thingToString`.
*/
return U.unstraighten(JSON.parse(string));
}
};
var circular = {
val: 'haha',
val2: [ 'hey', 'ho', 'hee' ],
doesNullWork: null
};
circular.circle1 = circular;
circular.confusing = {
circular: circular,
value: circular.val2
};
console.log('Does JSON.stringify work??');
try {
var str = JSON.stringify(circular);
console.log('JSON.stringify works!!');
} catch(err) {
console.log('JSON.stringify doesn\'t work!');
}
console.log('');
console.log('Does U.thingToString work??');
try {
var str = U.thingToString(circular);
console.log('U.thingToString works!!');
console.log('Its result looks like this:')
console.log(str);
console.log('And here\'s it converted back into an object:');
var obj = U.stringToThing(str);
for (var k in obj) {
console.log('`obj` has key "' + k + '"');
}
console.log('Did `null` work?');
if (obj.doesNullWork === null)
console.log('yes!');
else
console.log('nope :(');
} catch(err) {
console.error(err);
console.log('U.thingToString doesn\'t work!');
}
The whole idea is to serialize some circular structure by placing every object within directly into an array.
E.g. if you have an object like this:
{
val: 'hello',
anotherVal: 'hi',
circular: << a reference to itself >>
}
Then U.straighten will produce this structure:
[
0: {
val: 1,
anotherVal: 2,
circular: 0 // Note that it's become possible to refer to "self" by index! :D
},
1: 'hello',
2: 'hi'
]
Just a couple of extra notes:
I've been using these functions for quite some time in a wide variety of situations! It's very unlikely there are hidden bugs.
The O(n^2) runtime issue could be defeated with an ability to map every object to a unique hash value (which can be implemented). The reason for the O(n^2) nature is a linear search must be used to find items that have already been circularized. Because this linear search is occurring within an already linear process, the runtime becomes O(n^2)
These methods actual provide a small amount of compression! Inline values that are the same will not occur twice at different indexes. All same instances of an inline value will be mapped to the same index. E.g.:
{
hi: 'hihihihihihihihihihihi-very-long-pls-compress',
ha: 'hihihihihihihihihihihi-very-long-pls-compress'
}
Becomes (after U.straighten):
[
0: {
hi: 1,
ha: 1
},
1: 'hihihihihihihihihihihi-very-long-pls-compress'
]
And finally, in case it wasn't clear using this code is very easy!! You only need to ever look at U.thingToString and U.stringToThing. The usage of these functions is precisely the same as the usage of JSON.stringify and JSON.parse.
var circularObj = // Some big circular object you have
var serialized = U.thingToString(circularObj);
var unserialized = U.stringToThing(serialized);

Related

Access sub-property with generic/dynamic property list [duplicate]

I have a bunch of object attributes coming in as dot-delimited strings like "availability_meta.supplier.price", and I need to assign a corresponding value to record['availability_meta']['supplier']['price'] and so on.
Not everything is 3 levels deep: many are only 1 level deep and many are deeper than 3 levels.
Is there a good way to assign this programmatically in Javascript? For example, I need:
["foo.bar.baz", 1] // --> record.foo.bar.baz = 1
["qux.qaz", "abc"] // --> record.qux.qaz = "abc"
["foshizzle", 200] // --> record.foshizzle = 200
I imagine I could hack something together, but I don't have any good algorithm in mind so would appreciate suggestions. I'm using lodash if that's helpful, and open to other libraries that may make quick work of this.
EDIT this is on the backend and run infrequently, so not necessary to optimize for size, speed, etc. In fact code readability would be a plus here for future devs.
EDIT 2 This is NOT the same as the referenced duplicate. Namely, I need to be able to do this assignment multiple times for the same object, and the "duplicate" answer will simply overwrite sub-keys each time. Please reopen!
You mentioned lodash in your question, so I thought I should add their easy object set() and get() functions. Just do something like:
_.set(record, 'availability_meta.supplier.price', 99);
You can read more about it here: https://lodash.com/docs#set
These functions let you do more complex things too, like specify array indexes, etc :)
Something to get you started:
function assignProperty(obj, path, value) {
var props = path.split(".")
, i = 0
, prop;
for(; i < props.length - 1; i++) {
prop = props[i];
obj = obj[prop];
}
obj[props[i]] = value;
}
Assuming:
var arr = ["foo.bar.baz", 1];
You'd call it using:
assignProperty(record, arr[0], arr[1]);
Example: http://jsfiddle.net/x49g5w8L/
What about this?
function convertDotPathToNestedObject(path, value) {
const [last, ...paths] = path.split('.').reverse();
return paths.reduce((acc, el) => ({ [el]: acc }), { [last]: value });
}
convertDotPathToNestedObject('foo.bar.x', 'FooBar')
// { foo: { bar: { x: 'FooBar' } } }
Just do
record['foo.bar.baz'] = 99;
But how would this work? It's strictly for the adventurous with a V8 environment (Chrome or Node harmony), using Object.observe. We observe the the object and capture the addition of new properties. When the "property" foo.bar.baz is added (via an assignment), we detect that this is a dotted property, and transform it into an assignment to record['foo']['bar.baz'] (creating record['foo'] if it does not exist), which in turn is transformed into an assignment to record['foo']['bar']['baz']. It goes like this:
function enable_dot_assignments(changes) {
// Iterate over changes
changes.forEach(function(change) {
// Deconstruct change record.
var object = change.object;
var type = change.type;
var name = change.name;
// Handle only 'add' type changes
if (type !== 'add') return;
// Break the property into segments, and get first one.
var segments = name.split('.');
var first_segment = segments.shift();
// Skip non-dotted property.
if (!segments.length) return;
// If the property doesn't exist, create it as object.
if (!(first_segment in object)) object[first_segment] = {};
var subobject = object[first_segment];
// Ensure subobject also enables dot assignments.
Object.observe(subobject, enable_dot_assignments);
// Set value on subobject using remainder of dot path.
subobject[segments.join('.')] = object[name];
// Make subobject assignments synchronous.
Object.deliverChangeRecords(enable_dot_assignments);
// We don't need the 'a.b' property on the object.
delete object[name];
});
}
Now you can just do
Object.observe(record, enable_dot_assignments);
record['foo.bar.baz'] = 99;
Beware, however, that such assignments will be asynchronous, which may or may not work for you. To solve this, call Object.deliverChangeRecords immediately after the assignment. Or, although not as syntactically pleasing, you could write a helper function, also setting up the observer:
function dot_assignment(object, path, value) {
Object.observe(object, enable_dot_assignments);
object[path] = value;
Object.deliverChangeRecords(enable_dot_assignments);
}
dot_assignment(record, 'foo.bar.baz', 99);
Something like this example perhaps. It will extend a supplied object or create one if it no object is supplied. It is destructive in nature, if you supply keys that already exist in the object, but you can change that if that is not what you want. Uses ECMA5.
/*global console */
/*members split, pop, reduce, trim, forEach, log, stringify */
(function () {
'use strict';
function isObject(arg) {
return arg && typeof arg === 'object';
}
function convertExtend(arr, obj) {
if (!isObject(obj)) {
obj = {};
}
var str = arr[0],
last = obj,
props,
valProp;
if (typeof str === 'string') {
props = str.split('.');
valProp = props.pop();
props.reduce(function (nest, prop) {
prop = prop.trim();
last = nest[prop];
if (!isObject(last)) {
nest[prop] = last = {};
}
return last;
}, obj);
last[valProp] = arr[1];
}
return obj;
}
var x = ['fum'],
y = [
['foo.bar.baz', 1],
['foo.bar.fum', new Date()],
['qux.qaz', 'abc'],
['foshizzle', 200]
],
z = ['qux.qux', null],
record = convertExtend(x);
y.forEach(function (yi) {
convertExtend(yi, record);
});
convertExtend(z, record);
document.body.textContent = JSON.stringify(record, function (key, value, Undefined) {
/*jslint unparam:true */
/*jshint unused:false */
if (value === Undefined) {
value = String(value);
}
return value;
});
}());
it's an old question, but if anyone still looking for a solution can try this
function restructureObject(object){
let result = {};
for(let key in object){
const splittedKeys = key.split('.');
if(splittedKeys.length === 1){
result[key] = object[key];
}
else if(splittedKeys.length > 2){
result = {...result, ...{[splittedKeys.splice(0,1)]: {}} ,...restructureObject({[splittedKeys.join('.')]: object[key]})}
}else{
result[splittedKeys[0]] = {[splittedKeys[1]]: object[key]}
}
}
return result
}

How do I write a recursive function in Javascript to add up all of the string values of a deeply nested object?

Say I have this object:
{
"prop1":"Hello",
"prop2":{
"prop1":{
"prop1":"Tablecloth",
"prop2":"Indians"
},
"prop2":"JuicyJuice"
},
"prop3":"Sponge",
"prop4":{"Bob":"Squarepants"}
}
I would like a recursive function that will return HelloTableclothIndiansJuicyJuiceSpongeSquarepants.
Whatever object I put it, I want it to cycle though until it gets all of the strings and adds them all up.
Thank you!
Here's a very simple implementation that should work for simple objects like this:
var walkProps = function(obj) {
var s = "";
for(var x in obj)
{
if(typeof obj[x] === "string")
s += obj[x];
else
s += walkProps(obj[x]);
}
return s;
}
Demonstration
Note, though, that that depends on the order in which for-in visits the properties on the object, which is not specified and can vary by engine and by how the object is constructed (for instance, the order in which the properties were added).
Update: With some slight modification, this can be used to return the values based on the alphabetical order of the keys. This method is not sensitive to implementation-dependent ordering of properties:
var walkProps = function(obj) {
var s = "", i = 0, keys = Object.keys(obj).sort(), i;
for(; i < keys.length; i++)
{
if(typeof obj[keys[i]] === "string")
s += obj[keys[i]];
else
s += walkProps(obj[keys[i]]);
}
return s;
}
So even if "prop3" comes before "prop2" it will still return the same output.
Demonstration
You would need to write a function that loops over an object's properties and see if they are a string, and then append the strings to an output. If the property is an object rather than a string, you would want to call the function on this object and append it's return value to your total output.
You can loop over an object's properties using for...in like:
var MyObject = {
'a': 'string1',
'b': 'string2'
};
for (var key in MyObject) {
var value = MyObject[key];
}
To check if a property is a string you would want to do:
typeof value === "string"
Which will return true/false accordingly.
As mentioned, for( var b in a ) may not preserve ordering:
// Return array of string values
function getStrings(a) {
if( typeof(a) == "string" ) return [a];
var list = [];
for( var b in a ) list = list.concat(getStrings(a[b]));
return list;
}
Applied to OP's data:
var a = {
"prop1":"Hello",
"prop2":{
"prop1":{
"prop1":"Tablecloth",
"prop2":"Indians"
},
"prop2":"JuicyJuice"
},
"prop3":"Sponge",
"prop4":{"Bob":"Squarepants"}
}
getStrings(a).join(); // "Hello,Tablecloth,Indians,JuicyJuice,Sponge,Squarepants"
// Or as asked for by OP (again, order is not guaranteed)
getStrings(a).join(''); // "HelloTableclothIndiansJuicyJuiceSpongeSquarepants"

Checking for duplicate Javascript objects

TL;DR version: I want to avoid adding duplicate Javascript objects to an array of similar objects, some of which might be really big. What's the best approach?
I have an application where I'm loading large amounts of JSON data into a Javascript data structure. While it's a bit more complex than this, assume that I'm loading JSON into an array of Javascript objects from a server through a series of AJAX requests, something like:
var myObjects = [];
function processObject(o) {
myObjects.push(o);
}
for (var x=0; x<1000; x++) {
$.getJSON('/new_object.json', processObject);
}
To complicate matters, the JSON:
is in an unknown schema
is of arbitrary length (probably not enormous, but could be in the 100-200 kb range)
might contain duplicates across different requests
My initial thought is to have an additional object to store a hash of each object (via JSON.stringify?) and check against it on each load, like this:
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
}
but I'm worried about having property names in myHashMap that might be 200 kb in length. So my questions are:
Is there a better approach for this problem than the hashmap idea?
If not, is there a better way to make a hash function for a JSON object of arbitrary length and schema than JSON.stringify?
What are the possible issues with super-long property names in an object?
I'd suggest you create an MD5 hash of the JSON.stringify(o) and store that in your hashmap with a reference to your stored object as the data for the hash. And to make sure that there are no object key order differences in the JSON.stringify(), you have to create a copy of the object that orders the keys.
Then, when each new object comes in, you check it against the hash map. If you find a match in the hash map, then you compare the incoming object with the actual object that you've stored to see if they are truly duplicates (since there can be MD5 hash collisions). That way, you have a manageable hash table (with only MD5 hashes in it).
Here's code to create a canonical string representation of an object (including nested objects or objects within arrays) that handles object keys that might be in a different order if you just called 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));
}
And, the algorithm
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);
}
}
}
Test case for jsonStringifyCanonical() is here: https://jsfiddle.net/jfriend00/zfrtpqcL/
Maybe. For example if You know what kind object goes by You could write better indexing and searching system than JS objects' keys. But You could only do that with JavaScript and object keys are written in C...
Must Your hashing be lossless or not? If can than try to lose compression (MD5). I guessing You will lose some speed and gain some memory. By the way, do JSON.stringify(o) guarantees same key ordering. Because {foo: 1, bar: 2} and {bar: 2, foo: 1} is equal as objects, but not as strings.
Cost memory
One possible optimization:
Instead of using getJSON use $.get and pass "text" as dataType param. Than You can use result as Your hash and convert to object afterwards.
Actually by writing last sentence I though about another solution:
Collect all results with $.get into array
Sort it with buildin (c speed) Array.sort
Now You can easily spot and remove duplicates with one for
Again different JSON strings can make same JavaScript object.

Find an item not in array

I have an array of objects that can have up to 6 products in them e.g.
var products = [{name:'Trampoline'}, {name:'Net'}, {name:'Tent'}, {name:'Hoop'}];
// missing Ladder & Anchor
I need a way to check through them, and have it tell me that 'Ladder' and 'Anchor' aren't in the array products. !$.inArray doesn't work (the jquery one).
Can anyone help?? Maybe my brain has just died for the day, cos I just can't figure it out.
I tried starting with an array of all the items it needs, but the first loop through just removes them all becase the first one is not an accessory.
this.getUpsellItem = function() {
var p = this.getProduct();
var slots = ['Net','Tent','Ladder','Basketball','Anchor'];
for(var i = 0; i< p.length; i++) {
if(p[i].display_name2.indexOf('Net') === -1) slots.splice(0,1);
if(p[i].display_name2.indexOf('Tent') === -1) slots.splice(1,1);
if(p[i].display_name2.indexOf('Anchor') === -1) slots.splice(3,1);
if(p[i].display_name2.indexOf('Ladder') === -1) slots.splice(2,1);
if(p[i].display_name2.indexOf('Basketball') === -1) slots.splice(4,1);
console.log(p[i].display_name2.indexOf('Basketball'))
}
console.log('Printing slots')
print_r(slots)
};
Since you're using jQuery we can use the handy jQuery.grep() function to return only the elements in slots that aren't present in products. $.grep takes a function that it uses to filter which elements in the array it should return and which it should discard. In this case we just test each item in slots using products.indexOf. Something like this should suffice:
var slots = [ 'Net', 'Tent', 'Ladder', 'Basketball', 'Anchor' ]
, products = [ { name: 'Trampoline' }, { name: 'Net' },
{ name: 'Tent' }, { name: 'Hoop' }
]
, missing = $.grep(slots, function(product) {
return products.indexOf({ name: product }) < 0 }
)
;
console.log(missing);
Your problem is that you have an array of objects:
var products = [{name:'Trampoline'}, {name:'Net'}, {name:'Tent'}, {name:'Hoop'}];
And you want to search based on a property of these objects. The indexOf method:
compares [...] using strict equality (the same method used by the ===, or triple-equals, operator)
So you won't find what you're looking for unless you have the specific object in hand, just searching based on the property value or an object with the same structure won't work.
jQuery's $.inArray utility function is (AFAIK) just a portability wrapper for JavaScript implementations that don't have an indexOf method in their Array.
You'll need a search function of your own, something like this:
function indexOfByProperty(array, property, value) {
for(var i = 0; i < array.length; ++i)
if(array[i][property] == value)
return i;
return -1;
}
You could also use === if you want to be stricter but that's up to you and what you need the function to do.
If your array is large, you are better off using a map rather than an array
var products = {"Trampoline": {name:'Trampoline'}, "Net": {name:'Net'}, etc..};
products["foo"] returns null
products["Trampoline"] returns {name: 'Trampoline'}
in O(1) time rather than O(n) time
In ES5 you can use Array.some() to drill into nested Objects in an array:
var products = [{name:'Trampoline'}, {name:'Net'}, {name:'Tent'}, {name:'Hoop'}];
var found_ladder = products.some(function(val, idx) {
return val.name === 'Ladder';
});
Javascript in_array function
function in_array (needle, haystack, argStrict) {
// Checks if the given value exists in the array
// * example : in_array('vlado', {0: 'Kevin', vlado: 'van', 1: 'Zonneveld'});
// * returns : false
// * example : in_array('van', ['Kevin', 'van', 'Zonneveld']);
// * returns : true
var key = '', strict = !! argStrict;
if (strict) {
for (key in haystack) {
if (haystack[key] === needle) { return true;}
}
} else {
for (key in haystack) {
if (haystack[key] == needle) { return true; }
}
}
return false;
}

How to determine if object is in array [duplicate]

This question already has answers here:
How do I check if an array includes a value in JavaScript?
(60 answers)
Closed 29 days ago.
I need to determine if an object already exists in an array in javascript.
eg (dummycode):
var carBrands = [];
var car1 = {name:'ford'};
var car2 = {name:'lexus'};
var car3 = {name:'maserati'};
var car4 = {name:'ford'};
carBrands.push(car1);
carBrands.push(car2);
carBrands.push(car3);
carBrands.push(car4);
now the "carBrands" array contains all instances.
I'm now looking a fast solution to check if an instance of car1, car2, car3 or car4 is already in the carBrands array.
eg:
var contains = carBrands.Contains(car1); //<--- returns bool.
car1 and car4 contain the same data but are different instances they should be tested as not equal.
Do I have add something like a hash to the objects on creation? Or is there a faster way to do this in Javascript.
I am looking for the fastest solution here, if dirty, so it has to be ;) In my app it has to deal with around 10000 instances.
no jquery
Use something like this:
function containsObject(obj, list) {
var i;
for (i = 0; i < list.length; i++) {
if (list[i] === obj) {
return true;
}
}
return false;
}
In this case, containsObject(car4, carBrands) is true. Remove the carBrands.push(car4); call and it will return false instead. If you later expand to using objects to store these other car objects instead of using arrays, you could use something like this instead:
function containsObject(obj, list) {
var x;
for (x in list) {
if (list.hasOwnProperty(x) && list[x] === obj) {
return true;
}
}
return false;
}
This approach will work for arrays too, but when used on arrays it will be a tad slower than the first option.
Why don't you use the indexOf method of javascript arrays?
Check this out: MDN indexOf Arrays
Simply do:
carBrands.indexOf(car1);
It will return you the index (position in the array) of car1. It will return -1 if car1 was not found in the array.
http://jsfiddle.net/Fraximus/r154cd9o
Edit: Note that in the question, the requirements are to check for the same object referenced in the array, and NOT a new object. Even if the new object is identical in content to the object in the array, it is still a different object.
As mentioned in the comments, objects are passed by reference in JS and the same object can exist multiple times in multiple structures.
If you want to create a new object and check if the array contains objects identical to your new one, this answer won't work (Julien's fiddle below), if you want to check for that same object's existence in the array, then this answer will work. Check out the fiddles here and in the comments.
Having been recently bitten by the FP bug reading many wonderful accounts of how neatly the functional paradigm fits with Javascript
I replicate the code for completeness sake and suggest two ways this can be done functionally.
var carBrands = [];
var car1 = {name:'ford'};
var car2 = {name:'lexus'};
var car3 = {name:'maserati'};
var car4 = {name:'ford'};
var car5 = {name:'toyota'};
carBrands.push(car1);
carBrands.push(car2);
carBrands.push(car3);
carBrands.push(car4);
// ES6 approach which uses the includes method (Chrome47+, Firefox43+)
carBrands.includes(car1) // -> true
carBrands.includes(car5) // -> false
If you need to support older browsers use the polyfill, it seems IE9+ and Edge do NOT support it. Located in polyfill section of MSDN page
Alternatively I would like to propose an updated answer to cdhowie
// ES2015 syntax
function containsObject(obj, list) {
return list.some(function(elem) {
return elem === obj
})
}
// or ES6+ syntax with cool fat arrows
function containsObject(obj, list) {
return list.some(elem => elem === obj)
}
try Array.prototype.some()
MDN Array.prototype.some
function isBiggerThan10(element, index, array) {
return element > 10;
}
[2, 5, 8, 1, 4].some(isBiggerThan10); // false
[12, 5, 8, 1, 4].some(isBiggerThan10); // true
You could use jQuery's grep method:
$.grep(carBrands, function(obj) { return obj.name == "ford"; });
But as you specify no jQuery, you could just make a derivative of the function. From the source code:
function grepArray( elems, callback, inv ) {
var ret = [];
// Go through the array, only saving the items
// that pass the validator function
for ( var i = 0, length = elems.length; i < length; i++ ) {
if ( !inv !== !callback( elems[ i ], i ) ) {
ret.push( elems[ i ] );
}
}
return ret;
}
grepArray(carBrands, function(obj) { return obj.name == "ford"; });
I used underscore javascript library to tweak this issue.
function containsObject(obj, list) {
var res = _.find(list, function(val){ return _.isEqual(obj, val)});
return (_.isObject(res))? true:false;
}
please refer to underscore.js documentation for the underscore functions used in the above example.
note: This is not a pure javascript solution. Shared for educational purposes.
You can just use the equality operator: ==. Objects are checked by reference by default, so you don't even need to use the === operator.
try this, just make sure you're using the correct variable reference in the place of car1:
var i, car, l = cars.length;
for (i = 0; i < l; i++)
{
if ((car = cars[i]) == car1)
{
break;
}
else car = null;
}
Edit to add:
An array extension was mentioned, so here's the code for it:
Array.prototype.contains = Array.prototype.contains || function(obj)
{
var i, l = this.length;
for (i = 0; i < l; i++)
{
if (this[i] == obj) return true;
}
return false;
};
Note that I'm caching the length value, as the Array's length property is actually an accessor, which is marginally slower than an internal variable.
I would use a generic iterator of property/value over the array. No jQuery required.
arr = [{prop1: 'val1', prop2: 'val2'}, {prop1: 'val3', prop2: 'val4'}];
objectPropInArray(arr, 'prop1', 'val3'); // <-- returns true
function objectPropInArray(list, prop, val) {
if (list.length > 0 ) {
for (i in list) {
if (list[i][prop] === val) {
return true;
}
}
}
return false;
}
You could try sorting the array based on a property, like so:
carBrands = carBrands.sort(function(x,y){
return (x == y) ? 0 : (x > y) ? 1 : -1;
});
Then you can use an iterative routine to check whether
carBrands[Math.floor(carBrands.length/2)]
// change carBrands.length to a var that keeps
// getting divided by 2 until result is the target
// or no valid target exists
is greater or lesser than the target, and so on, which will let you go through the array quickly to find whether the object exists or not.
try this ,
You can use the JavaScript some() method to find out if a JavaScript array contains an object.
<script>
// An array of objects
var persons = [{name: "Harry"}, {name: "Alice"}, {name: "Peter"}];
// Find if the array contains an object by comparing the property value
if(persons.some(person => person.name === "Peter")){
alert("Object found inside the array.");
} else{
alert("Object not found.");
}
</script>
EDIT 05/18/2022
The most simple way using ES6:
const arrayContainsObject = <T extends Record<string, unknown>>(array: T[], object: T) => {
return array.some(item => Object.keys(item).every(key => item[key] === object[key]))
}
Use like so:
const arr = [{
prop1: 'value1',
prop2: 'value2'
}]
const obj1 = {
prop1: 'value1',
prop2: 'value2'
}
const obj2 = {
prop2: 'value2',
prop1: 'value1'
}
const obj3 = {
prop0: 'value0',
prop1: 'value1'
}
arrayContainsObject(arr, obj1) // true
arrayContainsObject(arr, obj2) // true, even when props are arranged in different order
arrayContainsObject(arr, obj3) // false
Previous answer, don't use (because the order of props in an object needs to be identical)
const arr = [{
prop: 'value'
}]
const obj = {
prop: 'value'
}
arr.some((e) => Object.entries(e).toString() === Object.entries(obj).toString()) // true
i know this is an old post, but i wanted to provide a JQuery plugin version and my code.
// Find the first occurrence of object in list, Similar to $.grep, but stops searching
function findFirst(a,b){
var i; for (i = 0; i < a.length; ++i) { if (b(a[i], i)) return a[i]; } return undefined;
}
usage:
var product = $.findFirst(arrProducts, function(p) { return p.id == 10 });
This function is to check for a unique field.
Arg 1: the array with selected data
Arg 2: key to check
Arg 3: value that must be "validated"
function objectUnique( array, field, value )
{
var unique = true;
array.forEach(function ( entry )
{
if ( entry[field] == value )
{
unique = false;
}
});
return unique;
}
you can use Array.find().
in your case is going to look like this
carBrands.find(function(car){
let result = car.name === 'ford'
if (result == null){
return false;
} else {
return true
}
});
if car is not null it will return the javaScript Object which contains the string 'ford'
The issue with many of the answers here is that they will NOT find an object in an array that is equal to another object. They will only search for an EXISTING object that has a pointer to it in an array.
Quick fix using lodash to see if ANY equal object is in an array:
import _ from 'lodash';
_.find(carBrands, car1); //returns object if true, undefined if false
Working Plunker using this method: https://plnkr.co/edit/y2YX9o7zkQa2r7lJ
if its possible to use es6
carBrands.filter(carBrand => carBrand.name === carX.name).length > 0
if it's true there is a similarity
You can convert both the JSON objects to string and simply check if the bigger json contains the smaller json.
console.log(JSON.stringify(carBrands).includes(JSON.stringify(car1))); // true
console.log(JSON.stringify(carBrands).includes(JSON.stringify(car5))); // false
You could also a the findIndex
var carBrands = [];
var car1 = {name:'ford'};
var car2 = {name:'lexus'};
carBrands.push(car1);
if (carBrands.findIndex(f => f.name === car1.name) === -1) {
console.log('not contain')
} else {
console.log('contain')
}
if (carBrands.findIndex(f => f.name === car2.name) === -1) {
console.log('not contain')
} else {
console.log('contain')
}

Categories

Resources