Efficient way to find first occurrence of key:value inside object - javascript

Below is the object in which I need to find at least one occurrence of isSelected: true.
[
{
"isSelected": true,
"child": [
{
"isSelected": true,
"child": [
{
"isSelected": true,
"child": [
{
"isSelected": true
}
]
}
]
}
]
}
]
The above object can have n elements in it and each element can have n children and so on. For every element there will be an isSelected key with value "true/false".
I am trying to write a function in JavaScript that will return true if it finds at least one occurrence of isSelected key with true value.
Wrote below function using JSON.stringify() and search for string "isSelected:true" string in it
function hasIsSelected(data){
return (JSON.stringify(data)).search('"isSelected":true') > -1 ? true: false
}
Not sure if JSON.stringify() will be efficient for large objects.
Trying to find solution in JavaScript without using third party library.

You can use a recursive algorithm to check the "isSelected" value and loop over all the children :
function hasIsSelected(data) {
if (data.isSelected) {
return true;
}
if (data.child) {
for (var i = 0; i < data.child.length; i++) {
if (hasIsSelected(data.child[i])) {
return true;
}
}
}
return false;
}
var json = [...]; // Your value
hasIsSelected(json[0]);
EDIT :
Ok let's make a very simple benchmark for the worst case :
function createTestData(depth) {
var obj = { isSelected: depth === 0 };
if (depth > 0) {
obj.child = [createTestData(depth - 1)];
}
return obj;
}
var testData = [createTestData(1000)]; // Big object, the "true" value is in the deepest child.
function hasIsSelectedStrinfigy(data){
return (JSON.stringify(data)).search('"isSelected":true') > -1;
}
function hasIsSelectedRec(data) {
if (data.isSelected) {
return true;
}
if (data.child) {
for (var i = 0; i < data.child.length; i++) {
if (hasIsSelectedRec(data.child[i])) {
return true;
}
}
}
return false;
}
// Using NicolaeS's solution
function hasIsSelectedRecTOC(data) {
if (data.isSelected === true) {
return true;
}
if (data.child instanceof Array) {
// stops after the first valid element
return data.child.some(hasIsSelectedRecTOC);
}
return false;
}
// Run tests
function runTest(fun) {
var t0 = performance.now();
fun(testData[0]);
var t1 = performance.now();
return t1 - t0;
}
console.log("Exec time using stringify : %o", runTest(hasIsSelectedStrinfigy));
console.log("Exec time using recursion : %o", runTest(hasIsSelectedRec));
console.log("Exec time using recursion with TOC : %o", runTest(hasIsSelectedRecTOC));
Results on my computer (change every time you run them but you get the idea) :
Exec time using stringify : 6.785000000000004
Exec time using recursion : 0.36999999999999034
Exec time using recursion with TOC : 0.37999999999999545
This was for the worst case. Now with the best case (the first isSelected is "true") :
function createTestData(depth) {
var obj = { isSelected: true }; // isSelected is always true
if (depth > 0) {
obj.child = [createTestData(depth - 1)];
}
return obj;
}
var testData = [createTestData(1000)];
Results :
Exec time using stringify : 3.980000000000002
Exec time using recursion : 0.040000000000000924
Exec time using recursion with TOC : 0.02499999999999858

Building on the answer of #Junior - recursion is the fastest way to do it, but here is a more performant version using tail call optimization:
function hasIsSelected(data) {
if (data.isSelected === true) {
return true;
} else if (data.child instanceof Array) {
return data.child.some(hasIsSelected); // stops after the first selected element
} else return false;
}
Another important trick is to stop the loop as soon as a true is found.

Recursion will be the best way to do that:
const deepSearch = (arr) => {
return arr.some((v) => {
if (v.isSelected === true) {
return true;
}
if (Array.isArray(v.child) && v.child.length > 0) {
return deepSearch(v.child);
}
return false;
});
};
Here is jsperf test.
Added: Array.isArray(X) is ≈3.3 times faster than X instanceof Array. Here is jsperf test confirming that.

Related

Replace multiple statements

Given the following code:
if (CONDITION1) {
if (CONDITION2) {
if (CONDITION3) {
if (CONDITION4) {
if (CONDITION5) {
} else {
return ('STOP IN CONDITION5')
}
} else {
return ('STOP IN CONDITION4')
}
} else {
return ('STOP IN CONDITION3')
}
} else {
return ('STOP IN CONDITION2')
}
} else {
return ('STOP IN CONDITION1')
}
I need to replace these multiple if statements for some other code using good practices and for cleaner reading for other people in future.
You could flatten this by testing for not condition:
function doStuff() {
if (!CONDITION1) {
return ('STOP IN CONDITION1')
}
if (!CONDITION2) {
return ('STOP IN CONDITION2')
}
if (!CONDITION3) {
return ('STOP IN CONDITION3')
}
if (!CONDITION4) {
return ('STOP IN CONDITION4')
}
if (!CONDITION5) {
return ('STOP IN CONDITION5')
}
}
Note: in between the ifs you could add code for condition true.
This has already been answered and I wouldn't recommend this version (linters probably complain, flat if statements are fine) but I'm throwing it in there to show another possibility.
You can potentially use a switch statement and add your conditions to each case:
const CONDITION1 = 1 > 2
const CONDITION2 = 2 < 1
const run = () => {
switch(true) { // always evaluate this switch
case !CONDITION1:
return 'STOP IN CONDITION1'
case !CONDITION2:
return 'STOP IN CONDITION1'
// etc...
}
}
console.log(run())
Using Array.prototype.findIndex()
Store your conditions into Array
Use Array.prototype.findIndex(). Just return the argument itself (x) => x, it fill be treated as boolean and if true the method will return the index. Store the returned integer into a variable errIndex.
If errIndex is greater than -1 that means you have an "error" in the conditions array at that index
const conditions = [false, false, true, false, false];
const errIndex = conditions.findIndex(x => x);
if (errIndex > -1) {
console.log(`STOP IN CONDITION ${errIndex + 1}`);
} else {
console.log("All passed");
}
The nice thing about using .findIndex() is in that it can short-circuit the search and immediately return the index.
Not sure why, but given you want to return a String, and since return statements are used within a function, and since it's always preferable for a function to return the same type,
you could create a function that accepts an array of conditions and a success string as the second argument:
const status = (conditions, successMsg) => {
const idx = conditions.findIndex(x=>x);
return idx < 0 ? successMsg : `STOP IN CONDITION ${idx+1}`;
};
console.log(status([false, false, true, false, false], "OK")); // STOP IN CONDITION 3
console.log(status([false, false, false, false, false], "OK")); // OK
or you can create something similar based on the above idea, like a pass function in which you do not hardcode error strings, but instead it provides you with a callback function with argument being an Object with index property or null
const pass = (conditions, fn) => {
const index = conditions.findIndex(x => x);
fn(index < 0 ? null : {index});
};
pass([false, false, true, false, false], (err) => {
if (err) {
console.log(`STOP IN CONDITION ${err.index + 1}`)
return; // Exit function here
}
// All OK...
console.log("All OK! Do some work here");
});

Validating if object data is "true" and retrieving relevant data if so

I have a problem to solve using some data from an object. The data could take a few forms and may or may not exist in the first place. For example
things : {
oranges: true,
apples : false
}
but it could equally be:
things : {
oranges: false,
apples : false
}
or maybe things doesn't even exist
I need to:
1) Determine that things exists
2) Determine that things contains further keys
These two statements need to be verified in one callable function e.g thingsHasData()
3) If things does have data, is any of the data set to true?
This also needs to be a callable function e.g fruitsIsTrue()
4) Return the key for one of the true values
trueFruit() - this should only return one key, but it doesn't matter which (it shouldn't ever have two true values as per business rules but it's more of a fallback to just return one if for some reason it does)
So I've been able to get the key of a true key-value pair using the following:
var thingsList = {
things : {
oranges: false,
apples : true
}
}
var trueFruit = Object.keys(thingsList).filter(function(key) {
return thingsList[key];
});
return thingsList[0];
This correctly returns apples and only apples so it works for point 4 but not the others, and I feel like there is a better way to do this not having to rely on repeating the same .filter in a few different functions. Ideas?
You could take functions and for a true value, use Array#find.
function thingsHasData(object) {
return 'things' in object;
}
function fruitsIsTrue(object) {
return 'things' in object && Object.values(object.things).some(Boolean);
}
function trueFruit(object) {
return 'things' in object && Object.keys(object.things).find(k => object.things[k]);
}
var a = {},
b = { things: {} },
c = { things: { oranges: true, apples : false } },
d = { things: { oranges: false, apples : false } };
[a, b, c, d].forEach(o => console.log(
thingsHasData(o),
fruitsIsTrue(o),
trueFruit(o)
));
To check if the Object things exist, you can use the following code:
if (typeof things != "undefined") {
// It exists!
}
To check if an object has any children, check Object.keys(things).length > 0.
So the check for 1) and 2) would look like:
let things = {
oranges: true,
apples: false
}
if (typeof things != "undefined") {
// It exists!
if (Object.keys(things).length > 0) {
// It has children!
}
}
var thingsList = {
things : {
oranges: false,
apples : true
},
things2 : {
oranges: true,
apples : true
}
};
function validateThings(things) {
// (1) checks for a falsy value of things
if (!things) {
return false;
}
var keys = Object.keys(things);
// (2) checks if things has keys
if (!keys.length) {
return false;
}
// (3) then it checks for every single keys value if it is truthy
for (var i = 0, len = keys.length; i < len; i++ ) {
if (things[keys[i]]) {
// (4) return this value — all tests passed
return things[keys[i]];
}
}
return false;
}
console.log(validateThings(thingsList.notInList));
console.log(validateThings(thingsList.things));
console.log(validateThings(thingsList.things2));
const thingsHasData = arg => (arg.things && Object.keys(arg.things).length>0) ? true : false;
const trueFruit = arg => {
if (!arg.things) return;
let fruitIndex = null;
let fruitValues = Object.values(arg.things);
fruitValues.forEach((value, index) => {
if (value) fruitIndex = Object.keys(arg.things)[index];
});
return fruitIndex;
}

Object has-property-deep check in JavaScript

Let's say we have this JavaScript object:
var object = {
innerObject:{
deepObject:{
value:'Here am I'
}
}
};
How can we check if value property exists?
I can see only two ways:
First one:
if(object && object.innerObject && object.innerObject.deepObject && object.innerObject.deepObject.value) {
console.log('We found it!');
}
Second one:
if(object.hasOwnProperty('innerObject') && object.innerObject.hasOwnProperty('deepObject') && object.innerObject.deepObject.hasOwnProperty('value')) {
console.log('We found it too!');
}
But is there a way to do a deep check? Let's say, something like:
object['innerObject.deepObject.value']
or
object.hasOwnProperty('innerObject.deepObject.value')
There isn't a built-in way for this kind of check, but you can implement it easily. Create a function, pass a string representing the property path, split the path by ., and iterate over this path:
Object.prototype.hasOwnNestedProperty = function(propertyPath) {
if (!propertyPath)
return false;
var properties = propertyPath.split('.');
var obj = this;
for (var i = 0; i < properties.length; i++) {
var prop = properties[i];
if (!obj || !obj.hasOwnProperty(prop)) {
return false;
} else {
obj = obj[prop];
}
}
return true;
};
// Usage:
var obj = {
innerObject: {
deepObject: {
value: 'Here am I'
}
}
}
console.log(obj.hasOwnNestedProperty('innerObject.deepObject.value'));
You could make a recursive method to do this.
The method would iterate (recursively) on all 'object' properties of the object you pass in and return true as soon as it finds one that contains the property you pass in. If no object contains such property, it returns false.
var obj = {
innerObject: {
deepObject: {
value: 'Here am I'
}
}
};
function hasOwnDeepProperty(obj, prop) {
if (typeof obj === 'object' && obj !== null) { // only performs property checks on objects (taking care of the corner case for null as well)
if (obj.hasOwnProperty(prop)) { // if this object already contains the property, we are done
return true;
}
for (var p in obj) { // otherwise iterate on all the properties of this object.
if (obj.hasOwnProperty(p) && // and as soon as you find the property you are looking for, return true
hasOwnDeepProperty(obj[p], prop)) {
return true;
}
}
}
return false;
}
console.log(hasOwnDeepProperty(obj, 'value')); // true
console.log(hasOwnDeepProperty(obj, 'another')); // false
Alternative recursive function:
Loops over all object keys. For any key it checks if it is an object, and if so, calls itself recursively.
Otherwise, it returns an array with true, false, false for any key with the name propName.
The .reduce then rolls up the array through an or statement.
function deepCheck(obj,propName) {
if obj.hasOwnProperty(propName) { // Performance improvement (thanks to #nem's solution)
return true;
}
return Object.keys(obj) // Turns keys of object into array of strings
.map(prop => { // Loop over the array
if (typeof obj[prop] == 'object') { // If property is object,
return deepCheck(obj[prop],propName); // call recursively
} else {
return (prop == propName); // Return true or false
}
}) // The result is an array like [false, false, true, false]
.reduce(function(previousValue, currentValue, index, array) {
return previousValue || currentValue;
} // Do an 'or', or comparison of everything in the array.
// It returns true if at least one value is true.
)
}
deepCheck(object,'value'); // === true
PS: nem035's answer showed how it could be more performant: his solution breaks off at the first found 'value.'
My approach would be using try/catch blocks. Because I don't like to pass deep property paths in strings. I'm a lazy guy who likes autocompletion :)
JavaScript objects are evaluated on runtime. So if you return your object statement in a callback function, that statement is not going to be evaluated until callback function is invoked.
So this function just wraps the callback function inside a try catch statement. If it catches the exception returns false.
var obj = {
innerObject: {
deepObject: {
value: 'Here am I'
}
}
};
const validate = (cb) => {
try {
return cb();
} catch (e) {
return false;
}
}
if (validate(() => obj.innerObject.deepObject.value)) {
// Is going to work
}
if (validate(() => obj.x.y.z)) {
// Is not going to work
}
When it comes to performance, it's hard to say which approach is better.
On my tests if the object properties exist and the statement is successful I noticed using try/catch can be 2x 3x times faster than splitting string to keys and checking if keys exist in the object.
But if the property doesn't exist at some point, prototype approach returns the result almost 7x times faster.
See the test yourself: https://jsfiddle.net/yatki/382qoy13/2/
You can also check the library I wrote here: https://github.com/yatki/try-to-validate
I use try-catch:
var object = {
innerObject:{
deepObject:{
value:'Here am I'
}
}
};
var object2 = {
a: 10
}
let exist = false, exist2 = false;
try {
exist = !!object.innerObject.deepObject.value
exist2 = !!object2.innerObject.deepObject.value
}
catch(e) {
}
console.log(exist);
console.log(exist2);
Try this nice and easy solution:
public hasOwnDeepProperty(obj, path)
{
for (var i = 0, path = path.split('.'), len = path.length; i < len; i++)
{
obj = obj[path[i]];
if (!obj) return false;
};
return true;
}
In case you are writing JavaScript for Node.js, then there is an assert module with a 'deepEqual' method:
const assert = require('assert');
assert.deepEqual(testedObject, {
innerObject:{
deepObject:{
value:'Here am I'
}
}
});
I have created a very simple function for this using the recursive and happy flow coding strategy. It is also nice to add it to the Object.prototype (with enumerate:false!!) in order to have it available for all objects.
function objectHasOwnNestedProperty(obj, keys)
{
if (!obj || typeof obj !== 'object')
{
return false;
}
if(typeof keys === 'string')
{
keys = keys.split('.');
}
if(!Array.isArray(keys))
{
return false;
}
if(keys.length == 0)
{
return Object.keys(obj).length > 0;
}
var first_key = keys.shift();
if(!obj.hasOwnProperty(first_key))
{
return false;
}
if(keys.length == 0)
{
return true;
}
return objectHasOwnNestedProperty(obj[first_key],keys);
}
Object.defineProperty(Object.prototype, 'hasOwnNestedProperty',
{
value: function () { return objectHasOwnNestedProperty(this, ...arguments); },
enumerable: false
});

More efficient Javascript

Looking for another eye on making the following Javascript more efficient.
The following JSON is produced from a Resteasy service:
var testing = {
"com:klistret:cmdb:ci:pojo:successful":true,
"com:klistret:cmdb:ci:pojo:count":1,
"com:klistret:cmdb:ci:pojo:elements":{
"com:klistret:cmdb:ci:pojo:id":123,
"com:klistret:cmdb:ci:pojo:name":"Mars",
"com:klistret:cmdb:ci:pojo:type":{
"com:klistret:cmdb:ci:pojo:id":1,
"com:klistret:cmdb:ci:pojo:name":"Environment"
},
"com:klistret:cmdb:ci:pojo:configuration":{
"#www:w3:org:2001:XMLSchemainstance:type":"Environment",
"#Watermark":"past",
"com:klistret:cmdb:ci:commons:Name":"Mars"
}
}
};
Extended the Extjs JSONReader to handle key depths higher than 2 in the createAccessor method. Wondering if there is a way to make the code more efficient? The function below will be called like function(testing, "com:klistret:cmdb:ci:pojo:configuration.#Watermark") where the com:klistret:cmdb:ci:pojo:elements property is the root.
createAccessor : function(){
var re = /[\[\.]/;
return function(expr) {
if(Ext.isEmpty(expr)){
return Ext.emptyFn;
}
if(Ext.isFunction(expr)){
return expr;
}
# THIS FUNCTION I WANT TO BE EFFICIENT
return function(obj){
while (String(expr).search(re) !== -1) {
var i = String(expr).search(re);
var key = expr.substring(0, i);
if (obj.hasOwnProperty(key)) {
obj = obj[key];
}
expr = expr.substring(i+1, expr.length);
}
return obj[expr];
};
};
}()
This is what I use. I only allow dot annotation, mind:
Ext.override(Ext.data.JsonReader, {
createAccessor: function() {
return function(expr) {
if (Ext.isEmpty(expr)) {
return Ext.emptyFn;
} else if (Ext.isFunction(expr)) {
return expr;
} else {
return function(obj) {
var parts = (expr || '').split('.'),
result = obj,
part,
match;
while (parts.length > 0 && result) {
part = parts.shift();
match = part.match(/^(.+?)(\[(\d+)\])?$/);
result = result[match[1]];
if (result && match[3]) {
result = result[match[3]];
}
}
return result;
}
}
};
}()
});
A basic optimization would be to avoid scanning the string twice with search, which is pretty slow.
The best you could do is replace all the string scanning and substring extraction with a single call to expr.split('.'), which would support accessors of the form aaa.bbb.ccc.ddd and turn them into an array like ['aaa','bbb','ccc','ddd']. The other two characters you seem to support ([ and ]) wouldn't work.
Alternately, you could do an initial match for /[^\].[]+/g over your entire string and keep the matches to obtain a similar array, but this would possibly be slower than the previous solution.

javascript recursion in loop return statement not working

I have a loop in which I have a recursive function which does not return and stop. Here is code
var obj = {
here: { is: "an" },
object: 2
};
var obj1 = {
here: { is: "aan" },
object: 2
};
function objectTester(x) {
if (typeof x === 'object' && x !== null)
return true;
else
return false;
}
function deepEqual(valOne, valTwo) {
debugger
if (objectTester(valOne) && objectTester(valTwo)) {
for (key in valOne) {
if (valTwo.hasOwnProperty(key)) {
if (objectTester(valOne[key]) && objectTester(valTwo[key])) {
deepEqual(valOne[key], valTwo[key]);
}
else {
if (valOne[key] === valTwo[key]) {
}
else {
return false; //function dose not stop executing and return false
}
}
}
else {
return false;
}
}
return true;
}
else {
return false;
}
}
console.log(deepEqual(obj, obj1));
Link
When you call deepEqual recursively here:
if (objectTester(valOne[key]) && objectTester(valTwo[key])) {
deepEqual(valOne[key], valTwo[key]);
}
you are ignoring the return value from the recursive call so its gonna loop over everything irrespective of the inner objects being equal or not.
Try
if(!deepEqual(valOne[key], valTwo[key])){ return false }
Another alternative is throwing an exception (and catching it at the top) instead of returning booleans. This makes your control flow jump straight out no matter how many levels of recursion you have.

Categories

Resources