I'm making a game, and I've come across a problem... When I try to save, JSON fails and reports that circular reference is being made somewhere. I don't think it actually is, I can't see it, so is there an algorithm or anything which could tell me where it is exactly (between which objects and stuff)? Also, is there a JSON alternative that can save circular reference? I'm running a node.js server, I saw this, but I can't get it to work (it's not made as a module i can require() in my code).
If you want to serialize a circular reference so you can save it, you need to make the reference "virtual" in that it can't be serialized as a circular reference, since that would cause serialization to serialize the same circle of objects forever (or at least until the runtime has run out of memory).
So instead of storing the circular reference itself, you just store a pointer to the object. The pointer will just be something like ref : '#path.to.object' that can be resolved when you deserialize so you point the reference back to the actual object. You just need to break the reference on serialization to be able to serialize it.
Discovering a circular reference in JavaScript can be done by recursively iterating through all objects (with for (x in y)), store x in an array and compare each x with the identity operator (a.k.a. strict comparison operator) === for each z in the temporary array. Whenever x === z equals true, replace the reference to x with a placeholder that will be serialized to the above mentioned ref.
An alternative to keeping an array over "visited" objects is to "taint" the objects you iterate through by setting a property on them, like in this very naïve example:
for (x in y) {
if (x.visited) {
continue;
}
x.visited = true;
}
There is no good way to detect circularity in objects but it is possible though by walking the object tree and checking references. I baked up a node-walking function that tries to detect if a node has been already used as its parent
function isCircularObject(node, parents){
parents = parents || [];
if(!node || typeof node != "object"){
return false;
}
var keys = Object.keys(node), i, value;
parents.push(node); // add self to current path
for(i = keys.length-1; i>=0; i--){
value = node[keys[i]];
if(value && typeof value == "object"){
if(parents.indexOf(value)>=0){
// circularity detected!
return true;
}
// check child nodes
if(arguments.callee(value, parents)){
return true;
}
}
}
parents.pop(node);
return false;
}
And the usage would be isCircularObject(obj_value) where the function returns true if circularity exists and false if not.
// setup test object
var testObj = {
property_a:1,
property_b: {
porperty_c: 2
},
property_d: {
property_e: {
property_f: 3
}
}
}
console.log(isCircularObject(testObj)); // false
// add reference to another node in the same object
testObj.property_d.property_e.property_g = testObj.property_b;
console.log(isCircularObject(testObj)); // false
// add circular node
testObj.property_b.property_c = testObj.property_b;
console.log(isCircularObject(testObj)); // true
The key point being that an object value is equal with an other value only if it is the same object reference and not when it's another object (even if completely similar).
This is a small extension to Andris' answer that tells you where the first circular element is so you can deal with it accordingly.
function findCircularObject(node, parents, tree){
parents = parents || [];
tree = tree || [];
if (!node || typeof node != "object")
return false;
var keys = Object.keys(node), i, value;
parents.push(node); // add self to current path
for (i = keys.length - 1; i >= 0; i--){
value = node[keys[i]];
if (value && typeof value == "object") {
tree.push(keys[i]);
if (parents.indexOf(value) >= 0)
return true;
// check child nodes
if (arguments.callee(value, parents, tree))
return tree.join('.');
tree.pop();
}
}
parents.pop();
return false;
}
If you don't want a string, the tree array is unnecessary. Just change the original function to
return value;
for the circular object itself or
return parents.pop();
for its parent.
I was thinking about what you're trying to accomplish based off the initial code from your other question. Why not do something like this.
Player = function()
{
this.UnitTypeXpower = 2
this.UnitTypeYpower = 7
}
UnitTypeXAdd = function(owner)
{
owner.UnitTypeXpower++;
}
That way you don't have to use a circular reference and it accomplishes the same thing.
Here is the code that I am using to detect circular references, it uses the technique that was suggested in the accepted answer by asbjornu, whereby each value is walked through and its reference is maintained in an array so that the next value can be compared with those previously walked.
function isCircular(obj, arr) {
"use strict";
var type = typeof obj,
propName,
//keys,
thisVal,
//iterKeys,
iterArr,
lastArr;
if (type !== "object" && type !== "function") {
return false;
}
if (Object.prototype.toString.call(arr) !== '[object Array]') {
//if (!Array.isArray(arr)) {
type = typeof arr; // jslint sake
if (!(type === "undefined" || arr === null)) {
throw new TypeError("Expected attribute to be an array");
}
arr = [];
}
arr.push(obj);
lastArr = arr.length - 1;
for (propName in obj) {
//keys = Object.keys(obj);
//propName = keys[iterKeys];
//for (iterKeys = keys.length - 1; iterKeys >= 0; iterKeys -= 1) {
thisVal = obj[propName];
//thisVal = obj[keys[iterKeys]];
type = typeof thisVal;
if (type === "object" || type === "function") {
for (iterArr = lastArr; iterArr >= 0; iterArr -= 1) {
if (thisVal === arr[iterArr]) {
return true;
}
}
// alternative to the above for loop
/*
if (arr.indexOf(obj[propName]) >= 0) {
return true;
}
*/
if (isCircular(thisVal, arr)) {
return true;
}
}
}
arr.pop();
return false;
}
This code is available on jsfiddle, where you can test it for yourself.
I have also run some performance tests on jsperf.
Array.indexOf was only introduced as of Javascript 1.6, see MDN page
Array.isArray was only introduced as of Javascript 1.8.5, see MDN page
Object.keys was only introduced as of Javascript 1.8.5, see MDN page
It is also worth noting that arguments.callee is deprecated and forbidden in strict mode in preference to using named functions
This thread already contains some good answers. If you are looking for a way to detect circular references or compare two values while dealing with circular references, you should check out this library:
https://www.npmjs.com/package/#enio.ai/data-ferret
It has the following methods:
hasCircularReference(someValue) // A predicate that returns true when it detects circular reference.
isIdentical(valueA, valueB) // By calling setConfig(options) opt-in circular reference support, this function does an equality check that does not fall into an infinite recursion trap.
Under the hood, it uses a similar algorithm to what #Asbjørn Ulsberg describes, but cleans after itself by removing all flags inserted.
However, the primary difference between the algorithms discussed here and the suggestion is that it can deal with any number of native/custom, iterable/non-iterable classes, meaning it supports circular detection and value comparison beyond the JSON specification and JavaScript's Object and Array.
All that's required for it to handle other classes is to call:
registerClassTypes()
registerIterableClass()
This library comes with 100% code coverage, so you can check out how to use the API by reading the .spec files if you visit the GitHub page.
Disclaimer: I wrote this library, but I do think there is a legitimate reason to mention it as it gives additional features that you might need when dealing with circular dependencies.
Related
I have found a performance issue in my application in production env.In order to Reproduce the issue, I have write a sample code in local. I download the data from the pro env, and ran the sample with V8 profilling.At last I found that copyObject() in lodash occupies the most cpu time.The V8 profiling screenshot is at below.
This is the copyObject func source code:
function copyObject(source, props, object, customizer) {
var isNew = !object;
object || (object = {});
var index = -1,
length = props.length;
while (++index < length) {
var key = props[index];
var newValue = customizer
? customizer(object[key], source[key], key, object, source)
: undefined;
if (newValue === undefined) {
newValue = source[key];
}
if (isNew) {
baseAssignValue(object, key, newValue);
} else {
assignValue(object, key, newValue);
}
}
return object;
}
Btw, I use the _.defaults() in a for-loop, and input data is large.
Because the keys of the object I need is known, I replaced the _.defaults() with the following code, the cpu time reduced by half more.
const res = {
'key1': object.key1 || source1.key1 || source2.key1,
'key2': object.key2 || source1.key2 || source2.key2,
'key3': object.key3 || source1.key3 || source2.key3,
}
My question is what method in copyObject func cause the most cpu time? And is it normal? Thanks!
By looking at the lowdash code I see a number of slow things.
it uses Array.prototype.forEach to loop over an array of objects. forEach is slow as is and creates a new function scope.
It then makes an object out of the object in order to loop over its keys. The object() thing is for you waste of cpu and then looping over keys with for in is also very slow.
Finally it performs value check as well as checking hasOwnProperty which both are wasted on you.
In general it is always better to write your own set, clone, copy, default functions when you know the structure since a direct assign is faster than any thing else you can do.
NOTE: Careful with the double pipes (||) since 0 might be an OK value but 0 || 1 === 1. Other values also follow this rule. Just make an if (or ? : ) statement and you will be safer.
I am trying to check if an object is already in an array, following this answer here: How to determine if object is in array
I adjusted the function to suit my needs, and now it looks like this:
var _createDatesArray, _objInArray;
_objInArray = function(array, obj) {
var i;
i = 0;
while (i < array.length) {
console.log("array[i] == obj is ", array[i] === obj, " array[i] is ", array[i], " and obj is ", obj);
if (array[i] === obj) {
return true;
}
i++;
}
};
_createDatesArray = function(val) {
var result;
if (val != null) {
result = {
text: val
};
if (!_objInArray(scope.datesQuestion.dates, result)) {
scope.datesQuestion.dates.push(result);
}
return console.log(scope.datesQuestion.dates);
}
};
What I need to do, is basically see if the object is already in the array, and if is,t return true.
When debugging, the result of the console log is the following:
array[i] == obj is false array[i] is {text: "10/08/17"} and obj is
{text: "10/08/17"}
and the function says they are different (array[i] == obj is false) but they look the same to me.
I also checked the type of both, which is this:
typeof array[i] is "object"
typeof obj is "object"
can you help me with this? Why are they different? what can be different?
_createDatesArray is called when $scope of my angular app changes its value based on a ng-model, but I don't think this is relevant
They're two different objects with the same content. Comparing them with == or === will yield false.
Since you're using AngularJS, you can use angular.equals() instead to perform a deep comparison of the object's properties.
The objects you are comparing don't have the same reference, so == is returning false. See Object comparison in JavaScript for a more detailed explanation.
In this particular case, you could simply compare the text of dates to see if they are equivilant. However this wouldn't work for all objects like the function name suggests.
if (arr[i].text === obj.text)
Alternatively, you could create a method specific for checking if your array includes a given date and simplify it greatly using Array.prototype.some:
dateInArray = function (array, date) {
return array.some(function (arrayDate) {
return arrayDate.text === date.text
})
}
Or, more succinctly using ES6 arrow functions:
dateInArray = (array, date) => array.some(arrayDate => arrayDate.text === date.text)
array[i] === obj will return true ONLY if its the same object. In the link that you have referred the object being checked is the same object that is inserted in the array, thats why it returns true. In your case, you are creating a new object 'result' and adding the value in there. So the array does not contain the exact same object and hence returns false.
If 'text' is the only property in the object, instead of checking for the entire object you could check if the 'text' property in both the objects is same.
_objInArray = function(array, obj) {
var i;
i = 0;
while (i < array.length) {
if (array[i].text === obj.text) {
return true;
}
i++;
}
};
This happens because objects in JS are compared by reference, but not by values they have. But you need to compare objects by their value. So you need to get some third-party function or to write your own. One more option is to use angular built-in equals function.
angular.equals($scope.user1, $scope.user2);
For a better understanding you can read a good article on this subject here.
I need to determine whether a given object is either an Array, or typed array such as Float32Array.
Currently I'm checking whether the .length property is defined, but this isn't always indicative of an array. Similar issues arise with existence checking of .forEach() or other methods.
Several instanceof checks would suffice, as done here - but I'm wondering if there is a simple built-in feature, e.g., a generic Array.isArray() function that does what I need.
function isArrayOrTypedArray(x) {
return Boolean(x && (typeof x === 'object') && (Array.isArray(x) || (ArrayBuffer.isView(x) && !(x instanceof DataView)));
}
Unfortunately, I don't believe there is.
You can do the instanceof checks you mentioned, or you could check the result of Object.prototype.toString.call(variable) to see if it's one of the predefind strings ("[object Array]", "[object Uint8Array]", etc.). (Edit: Ah, I see by following the link in your question that that's also demonstrated by that code.)
While I think, as T.J. Crowder already said, there's no built-in function, you should be able to combine Array.isArray and ArrayBuffer.isView to get the functionality you want:
function isArrayOrTypedArray(x) {
return Array.isArray(x) || (ArrayBuffer.isView(x) &&
Object.prototype.toString.call(x) !== "[object DataView]");
}
Array.isArray(x) returns true if x is an array. ArrayBuffer.isView(x)returns true if x is a typed array or DataView, so we just need to ignore the case where x is a DataView to get the function we want.
Demonstration:
function isArrayOrTypedArray(x) {
return Array.isArray(x) || (ArrayBuffer.isView(x) && Object.prototype.toString.call(x) !== "[object DataView]");
}
console.log(isArrayOrTypedArray()); // false
console.log(isArrayOrTypedArray({})); // false
console.log(isArrayOrTypedArray(null)); // false
console.log(isArrayOrTypedArray(undefined)); // false
console.log(isArrayOrTypedArray(new ArrayBuffer(10))); // false
console.log(isArrayOrTypedArray([])); // true
console.log(isArrayOrTypedArray([1,2,3,4,5])); // true
console.log(isArrayOrTypedArray(new Uint8Array())); // true
console.log(isArrayOrTypedArray(new Float32Array())); // true
console.log(isArrayOrTypedArray(new Int8Array(10).subarray(0, 3))); // true
var buffer = new ArrayBuffer(2);
var dv = new DataView(buffer);
console.log(isArrayOrTypedArray(dv)); // false
You could do something like this:
function isArray(array) {
if((array.length || array.length === 0) && (array.constructor !== String)) {
return true;
}
return false;
}
Note that a String also has a length property and we need to exclude that, hence the constructor check.
To determine if x is an ArrayBuffer,
You can take advantage of the fact that new DataView(x) throws "TypeError: First argument to DataView constructor must be an ArrayBuffer" if x isn't an ArrayBuffer.
In other words, simply do:
function isArrayBuffer(x) {
try {
new DataView(x);
return true;
}
catch (TypeError) {
return false;
}
}
And to test if a thing is a TypedArray,
I believe ArrayBuffer.isView does the job.
You can use obj.constructor.name as a way of getting the object's name rather than an instanceof matching ladder.
What all these arrays have in common is they have Array in their classname and a array.length property which is a number.
function isArray(x) {
if (typeof x.length === 'number'
&& x.constructor.name.includes('Array')) {
return true;
}
return false;
}
This works in later versions of Javascript/Node anyway.
You could use name.includes if your JS version supports it.
Array.constructor.name is Array for [] and others like Uint8Array for typed arrays.
In other versions of JavaScript you may need obj.prototype.constructor.name.
How can I check if two ES2015 Map objects have the same set of (key, value) pairs?
We can assume that all the keys and values are primitive datatypes.
One approach to solve this would be to take the map.entries(), create array from it, then sort that array by keys. And do the same thing with the other map. And then loop through those two arrays to compare them. All this seams cumbersome and also very inefficient because of sorting (performance inefficiency) and because of making those arrays (memory inefficiency).
Does anybody have better idea?
There is no "standard" or "built-in" way to do this. Conceptually, you just have to compare that the two Map objects have the same keys and values for each key and have no extra keys.
To be as efficient about the comparison as possible, you can do the following optimizations:
First check the .size property on both maps. If the two maps don't have the same number of keys, then you know right away, they can't be identical.
Furthermore, guaranteeing that they have the same number of keys allows you to just iterate one of the maps and compare its values to the other.
Use the for (var [key, val] of map1) iterator syntax for iterating the keys so you don't have to build or sort an array of keys yourself (should be both faster and more memory efficient).
Then, lastly, if you make sure that the comparison returns immediately as soon as a mismatch is found, then it will shorten the execution time when they are not the same.
Then, since undefined is a legal value in a Map, but it's also what .get() returns if the key is not found, we have to watch out for that by doing an extra .has() if the value we're comparing is undefined.
Since both keys and values with a Map object can be objects themselves, this gets much trickier if you want a deep property comparison of objects to determine equality rather than just the more simple === that Javascript uses by default to test for the same object. Or, if you're only interested in objects that have primitives for keys and values, then this complexity can be avoided.
For a function that tests only strict value equality (checks objects to see if they are the same physical object, not a deep property comparison), you can do what is shown below. This uses ES6 syntax for efficient iteration of the map objects and attempts to improve performance when they do not match by short circuiting and returning false as soon as a mismatch is found.
"use strict";
function compareMaps(map1, map2) {
let testVal;
if (map1.size !== map2.size) {
return false;
}
for (let [key, val] of map1) {
testVal = map2.get(key);
// in cases of an undefined value, make sure the key
// actually exists on the object so there are no false positives
if (testVal !== val || (testVal === undefined && !map2.has(key))) {
return false;
}
}
return true;
}
// construct two maps that are initially identical
const o = {"k" : 2}
const m1 = new Map();
m1.set("obj", o);
m1.set("str0", undefined);
m1.set("str1", 1);
m1.set("str2", 2);
m1.set("str3", 3);
const m2 = new Map();
m2.set("str0", undefined);
m2.set("obj", o);
m2.set("str1", 1);
m2.set("str2", 2);
m2.set("str3", 3);
log(compareMaps(m1, m2));
// add an undefined key to m1 and a corresponding other key to m2
// this will pass the .size test and even pass the equality test, but not pass the
// special test for undefined values
m1.set("str-undefined", undefined);
m2.set("str4", 4);
log(compareMaps(m1, m2));
// remove one key from m1 so m2 has an extra key
m1.delete("str-undefined");
log(compareMaps(m1, m2));
// add that same extra key to m1, but give it a different value
m1.set("str4", 5);
log(compareMaps(m1, m2));
function log(args) {
let str = "";
for (let i = 0; i < arguments.length; i++) {
if (typeof arguments[i] === "object") {
str += JSON.stringify(arguments[i]);
} else {
str += arguments[i];
}
}
const div = document.createElement("div");
div.innerHTML = str;
const target = log.id ? document.getElementById(log.id) : document.body;
target.appendChild(div);
}
If you wanted to do deep object comparison rather than just comparing to see if they are physically the same object, where values could be objects or arrays, then life gets a lot more complicated.
To do that, you need a deep object comparison method that takes into account all of the following:
Recursive comparison for nested objects
Protection against circular references (which can cause an infinite loop)
Knowledge of how to compare some types of built-in objects such as a Date.
Since a lot has been written elsewhere about how to do a deep object comparison (including a number of highly voted answers here on StackOverflow), I will assume that is not the main part of your question.
If your Map has only string keys, then you can use this approach to compare them:
const mapToObj = (map) => {
let obj = Object.create(null)
for (let [k,v] of map) {
// We don’t escape the key '__proto__'
// which can cause problems on older engines
obj[k] = v
}
return obj
}
assert.deepEqual(mapToObj(myMap), myExpectedObj)
Note: deepEqual is part of many testing suites and if not, you can use lodash/underscore equivalents. Any function that does a deep comparison will do.
mapToObj function courtesy of http://exploringjs.com/es6/ch_maps-sets.html
Here's a one-liner function for checking map equality:
const mapsAreEqual = (m1, m2) => m1.size === m2.size && Array.from(m1.keys()).every((key) => m1.get(key) === m2.get(key));
The above won't work for Map<string, object> as in the following line will not evaluate two objects properly:
if (testVal !== val || (testVal === undefined && !map2.has(key))) {
The following version extends the function for Map<string, object> by comparing using JSON.stringify()
function compareMaps(map1, map2) {
var testVal;
if (map1.size !== map2.size) {
return false;
}
for (var [key, val] of map1) {
testVal = map2.get(key);
// in cases of an undefined value, make sure the key
// actually exists on the object so there are no false positives
if (JSON.stringify(testVal) !== JSON.stringify(val) || (testVal === undefined && !map2.has(key))) {
return false;
}
}
return true;
}
Here is my example with the ability to provide an optional compare function
/**
* The utility function that returns an intersection of two Sets
*
* #returns an array of items in common
*/
function intersection<T>(a: Set<T>, b: Set<T>): T[] {
return Array.from(a).filter(x => b.has(x));
}
/**
* Compares two Maps
*
* #param compare is an optional function for values comparison
* #returns `true` if they are equal, and `false` otherwise
*/
function compareMaps<T>(a: Map<string, T>, b: Map<string, T>, compare?: (aValue: T, bValue: T) => boolean): boolean {
const common = intersection(new Set(a.keys()), new Set(b.keys()));
return a.size === b.size &&
common.length === a.size &&
common.every(key =>
compare?.(a.get(key) as T, b.get(key) as T) ?? a.get(key) === b.get(key));
}
Note that the solution proposed by #jfriend00 does not work in typescript ES2016. See this for the right answer: iteration over a typescript map failing
I am writing one function on Javascript which needs to address all the anynymous types in a JSON object.
For example,
Typed= {
emails: [{email:'a#a.com'}, {email:'b#a.com'}, {email:'c#a.com'}, {email:'d#a.com'}]
};
is an example of typed array in a JSON because each element inside the array is typed email
while,
Anon= {
emails: ['a#a.com', 'b#a.com', 'c#a.com', 'd#a.com']
};
is a JSON object where emails is collection of some anonymous objects.
Is there any ways that I can differentiate between both in JQuery or Javascript?
The simplest solution is to have the JSON source only return one of the two forms. Then you don't have to branch in your client.
If that's not an option, you could get the values out with JavaScript's handy lazy-evaluation of boolean expressions:
var em = json.emails[0].email || json.emails[0];
That statement will prefer the array-of-objects version, but use the array-of-strings version as a fallback.
(edited in response to clarifying comment below)
You can determine what properties a JS object has at runtime like this:
function enumerate(targetObject){
var props = [];
for (var propName in targetObject ){
props.push(propName);
}
return props;
}
console.log(enumerate({foo:1, bar:'baz'}),join(',')); //"foo, bar"
you could then modulate your logic on the basis of the properties you get back. You'll want to make sure you understand prototypes (specifically what Object.hasOwnProperty does and means), too.
You can use Array iteration methods to quickly check if all (or some) elements of the array have the desired type:
Anon.emails.every(function(e) { return typeof e == "object" }) // false
Typed.emails.every(function(e) { return typeof e == "object" }) // true
or a more generic solution
typeCheck = function(type) {
return function() {
return typeof arguments[0] == type
}
}
Anon.emails.every(typeCheck("object")) // false
Typed.emails.every(typeCheck("object")) // true
(An obligatory warning about iteration methods not being supported in ancient browsers)
How about this:
var istyped = function (a) {
if (typeof(a) !== 'object') {
return false;
}
var count = 0;
for (var key in a) {
count = count + 1;
}
return (count === 1);
}
I'm assuming here you just want to distinguish between regular variables (this would be your anonymous variable) and objects with just one key/value pair inside (this would be your typed variable).
To check if array contains only typed variables you'd just have to loop through it with that function. For example (in newer versions of JavaScript):
Typed.emails.every(istyped) = true
Anon.emails.every(istyped) = false
Why not do a map first:
emails = emails.map(function (email) {
if (typeof email.email === 'string')
return email.email;
});
That will make your emails array an array of just strings. Then you can just process it as usual. There aren't any side-effects if it is an array of strings (email.email will be undefined).
I do stuff like this when I have to make one client deal with multiple versions of an API. Alternatively, you could do the map the other way:
emails = emails.map(function (email) {
if (typeof email === 'string')
return {email: email};
});
This would work better if there could be other information in each object in your emails array.