Related
I cannot get my for in loop to keep working after the first property in my object. This is a question from Eloquent JavaScript in Chapter 4:
Write a function, deepEqual, that takes two values and returns true
only if they are the same value or are objects with the same
properties whose values are also equal when compared with a recursive
call to deepEqual.
To find out whether to compare two things by identity (use the ===
operator for that) or by looking at their properties, you can use the
typeof operator. If it produces "object" for both values, you should
do a deep comparison. But you have to take one silly exception into
account: by a historical accident, typeof null also produces "object".
Here is my code:
function deepEqual(obj1, obj2) {
if ((typeof obj1 === 'object' && obj1 != null) && (typeof obj2 === 'object' && obj2 != null)) {
for (var property in obj1) {
if (property in obj2) {
return deepEqual(obj1[property], obj2[property])
} else {
return false;
}
}
} else if (obj1 !== obj2) {
return false;
} else {
return true;
}
}
var obj = {object: 3, here: 1};
var obj2 = {object: 3, here: 2};
console.log(deepEqual(obj, obj2));
The console returns true, when it should say false because the 'here' properties are not equal. When looking into the output, it's because the 'for in loop' in the function quits after the first property. Please help me as to why it's not continuing to loop.
your for loop can't move beyond the first property because you return out of the function when you call deepEqual
for (var property in obj1) {
if (property in obj2) {
// returning means no more looping....
return deepEqual(obj1[property], obj2[property])
}
you want to carry on looping if deepEqual returns that its equal, or return if its false.
The following function takes an object, loops through each value and returns false if the object or its children have an empty or undefined property web. Otherwise, it returns true:
hasNoCategories (object) {
for (let key in object) {
const value = object[key]
for (let i = 0; i < value.length; i++) {
const item = value[i]
if (item.web !== undefined && item.web !== '') return false
}
if (key === 'web' && value !== '') {
return false
}
}
return true
},
Example input:
{
"livingroom": [],
"garage": [],
"outdoors": [],
"other": [],
"id": "ZI4hteKxgr",
"name": "Cuiti",
"description": "",
"user": "",
"date": "2016/5/13",
}
How to rewrite this function without using for loops?
I'm not 100% sure what you expect the code to do, because your existing code and your description differ.
Your description is, rephrased, that this function checks whether object.web or any object.XXX.web are undefined. Your code however assumes that all members are arrays and checks whether object.web or object.XXX[YYY].web are undefined. (Note that it also doesn't do it correctly and accesses .length even though the member in question might be undefined.)
Since I'm not sure which of those is right, I'm providing two answers.
Functionality as per your textual description:
function hasNoCategories(object) {
if(!object.web) return false;
return Object.keys(object).every(function(key) {
if(typeof object[key] !== 'object') return true;
return !!object[key].web;
});
}
Functionality as per your existing code: (but with the length property access fixed)
function hasNoCategories(object) {
if(!object.web) return false;
return Object.keys(object).every(function(key) {
if(!Array.isArray(object[key])) return true;
return object[key].every(function(el) {
if(typeof object[key] !== 'object') return true;
return !!el.web;
});
});
}
To understand how this works, check out the documentation on Object.keys (which returns an array with the names of all keys in your object) and Array.prototype.every (which runs a callback function for every element in an array and returns true only if the callback returned true for every element).
Note that I'm assuming that your "empty or undefined" should reject all kinds of falsy values including null and the number (not string) zero. If not, then all the checks like if(!something) and return !!something would need to be changed to if(typeof something === "undefined" || something === '') and return typeof something !== "undefined" && something !== '', respectively.
Side note to prevent nitpicking: Of course there are still loops going on. But it was specifically asked "without for loop" and there is no for in this code.
I assume this is what you are looking for:
var hasNoCategories = function(object) {
if (!object.web) {
return false;
}
for (let key in object) {
var value = object[key];
if (!value.web) {
return false;
}
}
return true;
};
I got rid of 1 loop. But this cannot be done without loops because you have to loop over all children. You can hide this loop inside some another function but you cannot get rid of it.
If you really don't want to use loops (I don't know why), one of your options is to serialize the object and phrase the string for the word "web".
var s = JSON.stringify(object);
var webIndex = s.indexOf('web');
Now perform some checks around this index to ascertain if that has the value 'undefined' or ''. Please keep in mind that the word "web" can match as a part of another property name too. So, you need to include this possibility too to your checks.
I am trying to compare json_str1 and json_str2, here it should return true as all elements in json_str1 are present in json_str2.
For now I am doing this the long way like this
json_str1 = '{"0":"a","1":"b","2":"c"}';
json_str2 = '{"0":"c","1":"b","2":"a"}';
json_obj1 = $.parseJSON(json_str1);
json_obj2 = $.parseJSON(json_str2);
arr1 = $.map(json_obj1, function(el) { return el });
arr2 = $.map(json_obj2, function(el) { return el });
if($(arr1).not(arr2).length === 0 && $(arr2).not(arr1).length === 0)
alert("equal");
else
alert("not equal");
How could I make it short and simple, without converting the objects into an array ?
https://jsfiddle.net/kq9gtdr0/
Use the following code:
Object.keys(json_obj1) . every(k1 =>
Object.keys(json_obj2) . some(k2 =>
json_obj1[k1] === json_obj2[k2]
)
);
In English:
Every key k1 in json_obj1 satisfies the condition that some key k2 in json_obj2 satisifies the condition that the value of json_obj1 with key k1 is equal to the value of json_obj2 with key k2.
Or in more conversational English:
Every value in the first object matches some value in the second.
Using lodash
var _ = require('lodash');
function compareValues(jstr1, jstr2) {
return _.isEqual(_.valuesIn(JSON.parse(jstr1)).sort(), _.valuesIn(JSON.parse(jstr2)).sort());
}
json_str1 = '{"0":"a","1":"b","2":"c"}';
json_str2 = '{"0":"c","1":"b","2":"a"}';
console.log(compareValues(json_str1, json_str2));
There is short and easy accurate way to this.
You can use a third party but extremely popular utility library called Lodash. Chaining functions you can check for equality.
First parse both JSON into objects
Then use _.values() to extract the values of all keys of each into separate arrays
Find difference of two arrays. If its an empty array then both of them are equal.
You can chain all the steps into one statement like:
_.isEmpty(_.difference(_.values(json_obj1), _.values(json_obj2)))
Example: https://jsfiddle.net/kq9gtdr0/4/
For more information:
https://lodash.com/docs#values
https://lodash.com/docs#difference
https://lodash.com/docs#isEmpty
You can include the library from CDN(https://cdn.jsdelivr.net/lodash/4.5.1/lodash.min.js) or download and use it as normal script. Lodash offers plenty of useful utility functions that makes JS programming a lot easier. You better try it out.
If you prefer using libraries, then you could use underscore isMatch
_.isMatch(object, properties)
Tells you if the keys and values in properties are contained in
object.
Extending the awesome answer by #user663031, in case you need to do deep comparison, here's code that works:
export function objectOneInsideObjectTwo(jsonObj1: any, jsonObj2: any): boolean {
return Object.keys(jsonObj1).every((k1) => {
if (parseType(jsonObj1[k1]) === 'dict') {
return objectOneInsideObjectTwo(jsonObj1[k1], jsonObj2[k1]);
}
if (parseType(jsonObj1[k1]) === 'array') {
const results: boolean[] = [];
jsonObj1[k1].forEach((o: any, i: number) => {
if (parseType(o) === 'dict') {
results.push(objectOneInsideObjectTwo(o, jsonObj2[k1][i]));
} else {
results.push(o === jsonObj2[k1][i]);
}
});
return results.every((r) => r);
}
return Object.keys(jsonObj2).some((k2) => jsonObj1[k1] === jsonObj2[k2]);
});
}
export function parseType<T>(v: T): string {
if (v === null || v === undefined) {
return 'null';
}
if (typeof v === 'object') {
if (v instanceof Array) {
return 'array';
}
if (v instanceof Date) {
return 'date';
}
return 'dict';
}
return typeof v;
}
You can try this
var json_str1 = {"0":"a","1":"b","2":"c"};
var json_str2 = {"0":"c","1":"b","2":"a"};
var flag = 1;
if(Object.keys(json_str1).length == Object.keys(json_str2).length){
Object.keys(json_str1).forEach(function(x){
if(!json_str2.hasOwnProperty(x) || json_str2[x] != json_str1[x]){
flag = 0;
return;
}
});
}
if(flag)
alert('equal');
else
alert('Not Equal');
If you want to find out if both Objects have the same keys there is no way to do this without at least converting the keys of both Objects to an array with Object.keys or looping through both Objects!
The reason is simple: It's clear that you have to compare the number of keys of both Objects and the only way to do this is by looping through all properties or Object.keys.
So I think the shortest way to do this is:
json_obj1 = JSON.parse('{"0":"a","1":"b","2":"c"}');
json_obj2 = JSON.parse('{"0":"c","1":"b","2":"a"}');
keys_1 = Object.keys(json_obj1);
keys_2 = Object.keys(json_obj2);
if(keys_1.length === keys_2.length && keys_1.every(key => keys_2.indexOf(key) >= 0)) {
alert('equal')
} else {
alert('not equal')
}
If you only want to check if all keys from json1 are present in json2 you can do:
json_obj1 = JSON.parse('{"0":"a","1":"b","2":"c"}');
json_obj2 = JSON.parse('{"0":"c","1":"b","2":"a"}');
if(Object.keys(json_obj1).every(key => key in json_obj2)) {
alert('equal');
} else {
alert('not equal');
}
In your question and comments you indicate you are only looking to verify that "all elements in json_str1 are present in json_str2". Your example code doesn't just do that, it checks for the complete equality of keys by testing if all the keys (not values) in the first object are in the second object AND all the keys in the second object are in the first object. By looking at your code, i assume that when you say "elements" you mean keys.
All that aside, this might help:
// Your first few lines of code
json_str1 = '{"0":"a","1":"b","2":"c"}';
json_str2 = '{"0":"c","1":"b","2":"a"}';
json_obj1 = $.parseJSON(json_str1);
json_obj2 = $.parseJSON(json_str2);
// My new code
var values1 = Object.keys(json_obj1).map(key => json_obj1[key]);
var values2 = Object.keys(json_obj2).map(key => json_obj2[key]);
// Check if every key in the first object is in the second object.
values1.every(k1 => values2.indexOf(k1) >= 0);
// OR
// Check for equality of keys by checking both directions.
values1.every(k1 => values2.indexOf(k1) >= 0) && values2.every(k2 => values1.indexOf(k2) >= 0);
That's 2 lines to get the keys, and one line to check. You only need one of those two checks.
Is there a way to check if an object is "array-like", like for these types of objects:
Arrays (duh)
Typed Arrays (Uint8Array, etc), these will return false when Array.isArray is used
arguments object
NodeLists*
There are a few other ones that I can't think of off-hand
I suppose you could check for the presence of a .length property, but non-array-like objects can contain the .length property. I guess the thing these all share in common is the array accessor.
As best I've found in my research on this topic, you have only a couple choices:
You can look only at the .length property and accept any object that seems to have an appropriate .length property that isn't any other things you know you should eliminate (like a function).
You can check for specific array-like objects (HTMLCollection, nodeList) and bias in favor of them.
Here are two options for the first method - one that doesn't accept a zero length and one that does (these incorporate suggestions by gilly3 and things we see in jQuery's similar function):
// see if it looks and smells like an iterable object, but don't accept length === 0
function isArrayLike(item) {
return (
Array.isArray(item) ||
(!!item &&
typeof item === "object" &&
item.hasOwnProperty("length") &&
typeof item.length === "number" &&
item.length > 0 &&
(item.length - 1) in item
)
);
}
This, of course, reports false for items with .length === 0, If you want to allow .length === 0, then the logic can be made to include that case too.
// see if it looks and smells like an iterable object, and do accept length === 0
function isArrayLike(item) {
return (
Array.isArray(item) ||
(!!item &&
typeof item === "object" &&
typeof (item.length) === "number" &&
(item.length === 0 ||
(item.length > 0 &&
(item.length - 1) in item)
)
)
);
}
Some test cases: http://jsfiddle.net/jfriend00/3brjc/
2) After checking to see that it's not an actual array, you can code to check for specific kinds of array-like objects (e.g. nodeList, HTMLCollection).
For example, here's a method I use when I want to make sure I include nodeList and HTMLCollection array-like objects:
// assumes Array.isArray or a polyfill is available
function canAccessAsArray(item) {
if (Array.isArray(item)) {
return true;
}
// modern browser such as IE9 / firefox / chrome etc.
var result = Object.prototype.toString.call(item);
if (result === "[object HTMLCollection]" || result === "[object NodeList]") {
return true;
}
//ie 6/7/8
if (typeof item !== "object" || !item.hasOwnProperty("length") || item.length < 0) {
return false;
}
// a false positive on an empty pseudo-array is OK because there won't be anything
// to iterate so we allow anything with .length === 0 to pass the test
if (item.length === 0) {
return true;
} else if (item[0] && item[0].nodeType) {
return true;
}
return false;
}
You can check if the object is iterable :
function isIterable(o){
return (o!=null && typeof(o[Symbol.iterator])==='function');
}
Careful though, returns true for strings. If that's a problem, exclude them :
function isIterable(o){
return (o!=null && typeof(o[Symbol.iterator])==='function' && typeof(o)!=='string');
}
Then either access the elements using the iterator or if you want to use the regular array[0] way, just add a check for length property.
Complete isArrayLike function :
function isArrayLike(a){
return (
a!=null &&
typeof(a[Symbol.iterator])==='function' &&
typeof(a.length)==='number' &&
typeof(a)!=='string'
);
}
In JavaScript an array-like object is an object containing a length property typically of type number with a non-negative whole value no greater than 2^53-1. Array-like objects with lengths greater than zero also typically contain additional properties beginning at [0] and going up to [n-1] where n = the value of length.
From MDN:
The term array-like object refers to any object that doesn't throw during the length conversion process described [below]. In practice, such object is expected to actually have a length property and to have indexed elements in the range 0 to length - 1. (If it doesn't have all indices, it will be functionally equivalent to a sparse array.)
Normalization of the length property
The length property is converted to an integer and then clamped to the range between 0 and 2^53 - 1.
Array-like objects also differ from other objects in that Array methods such as Array.prototype.push can be called indirectly on them using Function.prototype.call().
Also from MDN:
Array methods cannot be called directly on array-like objects. But you
can call them indirectly using Function.prototype.call().
Calling Array functions on an object without a normalized length property changes the value of length. Therefore I thought it best that my function return false if the length property is not already an integer between 0 and 2^53 - 1. The length property cannot be a BigInt as that would throw a TypeError.
This function also checks for existence of the last element to help rule out objects that have a length property but aren't intended to be array-like.
Tested and works in old browsers (ES3 and up).
function isArrayLike(obj) {
var i;
return /( Arguments|NodeList|Collection|Array)\b/.test(({}).toString.call(obj))// Always array-like
// Confirm obj is an object but not a function or null
|| !!obj && typeof obj=="object"
// Confirm that Array methods can be called indirectly on obj
// without throwing or changing the value of obj.length
// by confirming obj.length (i) is a normalized integer
// Usually this means i is in the range 0 - 9007199254740991
&& [].push.call({length:Number(i=obj.length)})===i
// And when non-zero confirm i>0 and i-1 is a property in obj
&& (!i || i>0 && i-1 in obj);
}
Well, it depends what you mean by array-like. Probably something you could iterate with a for loop like this:
for (var i=0, l=obj.length; i<l; ++i) {
var item = obj[i];
}
So then the test is simple:
function isArrayLike(obj) {
if (!obj) return false;
var l = obj.length;
if (typeof l != 'number' || l < 0) return false;
if (Math.floor(l) != l) return false;
// fast check
if (l>0 && !((l-1) in obj)) return false;
// more complete check (optional)
for (var i=0; i<l; ++i) {
if (!(i in obj)) return false;
}
return true;
}
Of course, this won't catch arrays which are sparsely populated, but then again, are they really being used as arrays? NodeLists and the like won't be sparsely populated.
Enjoy!
There is a way to check if an object is array-like or not, even if there are no elements in it, using this function here:
isArrayLike = function (_) {
_[0] = 0; return [].slice.call(_).length >= Object.values(_).length;
}
This uses a little hack I accidentally discovered that allows you to determine if an object is (1) an array, (2) array-like, or (3) object / object-like.
The only downside is that it does not work correctly for array-likes that have object-like properties added, such as arguments
Technically, (pretty much) every object is "array-like" (because of type coercion of undefined) according to the standard (ECMAScript 2015 Language Specification §7.3.17, CreateListFromArrayLike (obj [, elementTypes] )):
7.3.17 CreateListFromArrayLike (obj [, elementTypes] )
The abstract operation CreateListFromArrayLike is used to create a List value whose elements are provided by the indexed properties of an array-like object, obj. The optional argument elementTypes is a List containing the names of ECMAScript Language Types that are allowed for element values of the List that is created. This abstract operation performs the following steps:
ReturnIfAbrupt(obj).
If elementTypes was not passed, let elementTypes be (Undefined, Null, Boolean, String, Symbol, Number, Object).
If Type(obj)
is not Object, throw a TypeError exception.
Let len be ToLength(Get(obj, "length")).
ReturnIfAbrupt(len).
Let list be an empty List.
Let index be 0.
Repeat while index < len
Let indexName be ToString(index).
Let next be Get(obj, indexName).
ReturnIfAbrupt(next).
If Type(next)
is not an element of elementTypes, throw a TypeError exception.
Append next as the last element of list.
Set index to index + 1.
Return list.
Generated via https://www.browserling.com/tools/html-to-markdown
An Array is a value that has following properties:
Its of type object
It has length property which is equal or greater than 0.
All the value based keys are numeric and is greater than or equal to 0.
Exceptions:
length
Value is a function.
function isArrayLike(value) {
if (typeof value === "object" && !!value) {
const isArray = Array.isArray(value);
const allNumericKeys = Object.keys(value).every((k) =>
(!isNaN(k) && +k >= 0) ||
k === "length" ||
typeof value[k] === "function"
)
const hasLength = value.length > 0
return isArray || (allNumericKeys && hasLength)
}
return false
}
console.log('Array: ', isArrayLike([]))
console.log('Array Like: ', isArrayLike({1: 'a', 2: 'b', length: 3}))
console.log('Array Like with function: ', isArrayLike({1: 'a', 2: 'b', length: 3, foo: () => {} }))
console.log('Array Like with negative keys: ', isArrayLike({ "-1": 'a', "-2": 'b', length: 1}))
console.log('Array Like without length:', isArrayLike({1: 'a', 2: 'b' }))
console.log('Node List: ', isArrayLike(document.querySelectorAll('p')))
console.log('null: ', isArrayLike(null))
console.log('String: ', isArrayLike('Test'))
console.log('Number: ', isArrayLike(123))
<div>
<p></p>
<p></p>
<p></p>
<p></p>
</div>
I say nothing beats the simplicity and expressiveness of extending native objects:
Object.prototype.isArrayLike = function(){ return false; };
Array.prototype.isArrayLike = function(){ return true; };
NodeList.prototype.isArrayLike = function(){ return true; };
HTMLCollection.prototype.isArrayLike = function(){ return true; };
This approach could cause conflicts between frameworks, however, I recommend keeping your distance from a framework whose isArrayLike function does not what the name suggests.
I have two objects. Their structure looks a bit like this:
{
education: ["school", "institute"],
courses: ["HTML", "JS", "CSS"],
Computer: {
"OS":"WXP",
"WS":"NotePad"
}
}
The second:
{
education: ["school", "university", "institute", "collage"],
courses: ["HTML", "CSS", "JS", "Managing", "Directing"],
Computer: {
"OS":"WXP",
"WS":"NotePad",
"AV":"Avast"
},
something: function(){...},
other: "thing"
}
As you may noticed, the second object containes the whole first object, plus some items that the first one doesn't have.
I need to compare these two objects, and get an answer(true-false) if the second objects containes every single item of the first object.
true - if all of the items of the first object are also in the second one
false - if at least one of the items of the first object is not also in the second one, for example: if the second object wouldn't have the "css" course.
(The first one is requirements, the second is what the person has. I need to check if the person has all of the requirements)
Could be plain JS, jQuery, whatever. I prefer not to use server-side languages for that.
is there a way of doing that?
THANKS!
Just recursively check it:
function isContainedIn(a, b) {
if (typeof a != typeof b)
return false;
if (Array.isArray(a) && Array.isArray(b)) {
// assuming same order at least
for (var i=0, j=0, la=a.length, lb=b.length; i<la && j<lb;j++)
if (isContainedIn(a[i], b[j]))
i++;
return i==la;
} else if (Object(a) === a) {
for (var p in a)
if (!(p in b && isContainedIn(a[p], b[p])))
return false;
return true;
} else
return a === b;
}
> isContainedIn(requirements, person)
true
For a more set-logic-like approach to arrays, where order does not matter, add something like
a.sort();
b = b.slice().sort()
(assuming orderable contents) before the array comparison loop or replace that by the quite inefficient
return a.every(function(ael) {
return b.some(function(bel) {
return isContainedIn(ael, bel);
});
});
JavaScript (in ES5) has two composite native types (I'm assuming you don't have any custom collections in your code, if you do - I assume they support the 'old' iteration protocol (having .length)
Here is an annotated sketch of a solution. I did not run this - it's there to get you an idea of how to implement this algorithm. Note that this enters an endless loop for back references (var a = {}; a.a =a}).
function sub(big,small){
if(typeof big === "function") return small === big; // function reference equality.
if(big.length){ // iterable, for example array, nodelist etc. (even string!)
if(small.length > big.length) return false; // small is bigger!
for(var i = 0; i < small.length; i++ ){
if(!sub(big[i],small[i])){ // doesn't have a property
return false;
}
}
return true; // all properties are subproperties recursively
}
if(typeof big === "object" && big !== null){
// I assume null is not a subset of an object, you may change this, it's conceptual
if(typeof small !== "object" || small === null) return false;
for(var key in small){
// I consider the prototype a part of the object, you may filter this with a
// hasOwnProperty check here.
if(!sub(big[key],small[key])){ // doesn't have a property
return false;
}
return true;
}
}
return big === small; // primitive value type equality
// , or ES7 value type equality, future compat ftw :P
}
Edit: didn't notice that merge changes the first argument... changed the code, but it still would cause obj2 to change. You can add _.cloneDeep(obj2) which should take care of that, but by then my solution doesn't seem as elegant. Updated the demo with cloneDeep as well.
Edit2: Since JSON.stringify requires the order of object properties to be the same in the objects you compare, you could instead use something like Object comparison in JavaScript. However, in the demo you can see that it works, so I would say there is a good chance that for your case, using _.merge with JSON.stringify is reliable.
With lo-dash, you can use _.merge and check whether the result is the same as the larger object.
function(obj1, obj2) {
var obj3 =_.merge(_.cloneDeep(obj2), obj1);
return JSON.stringify(obj3) === JSON.stringify(obj1);
}
demo
Of course, another option would be to iterate over the entire object with vanilla JS.
// When order of objects is not same
function isContainedIn(a, b) {
if (typeof a != typeof b)
return false;
if (Array.isArray(a) && Array.isArray(b)) {
if(a.length == 1) {
var j=0;
while (j < b.length) {
if ((isContainedIn( a[0], b[j]))) {
return true;
}
j++;
}
return false;
} else {
var k=0;
while (k < a.length) {
if (!(isContainedIn([a[k]], b))) {
return false;
}
k++;
}
return true;
}
} else if (Object(a) === a) {
for (var p in a)
if (!(p in b && isContainedIn(a[p], b[p])))
return false;
return true;
} else
return a === b;
};
isContainedIn(requirements, person)
true
In addition to Benjamin's answer - you could test this:
const sub = (big, small) => {
if (typeof big === 'function' || typeof small === 'string') return small === big; // function or string reference equality
if (big && big.length) { // iterable, for example array, nodelist etc. (even string!)
if (small.length > big.length) return false; // small is bigger!
for (let i = 0; i < small.length; i++)
if (!sub(big[i], small[i])) // doesn't have a property
return false;
return true; // all properties are subproperties recursively
}
if (typeof big === 'object' && big !== null) {
// I assume null is not a subset of an object, you may change this, it's conceptual
if (typeof small !== 'object' || small === null) return false;
// console.log(Object.keys(small));
for (const key of Object.keys(small)) {
// I consider the prototype a part of the object, you may filter this with a
// hasOwnProperty check here.
if (sub(big[key], small[key]) === false) // doesn't have a property
return false;
continue;
}
return true;
}
return big === small; // primitive value type equality
};
or even use a much cleaner solution:
https://github.com/blackflux/object-deep-contain