james2m
james2m

Reputation: 1580

Recursively find keys on an object

I have a javascript object structured like this;

brand: {
  group: {
    subGroup: {
       items: []
    },
    otherSub: {
       items: []
    }
  }
}

Given an array of keys ['brand', 'group', 'newGroup', 'newSubGroup'] I want to split the keys into found and missing keys. So for the structure above I should get back;

present = ['brand', 'group']
missing = ['newGroup', 'newSubGroup']

I'm using ES6 and have lodash available, but struggling to find a clean way to produce this.

This is not to just check existence, it's recursively find the keys and return those present and the remaining ones.

Upvotes: 0

Views: 789

Answers (7)

Lukas Bach
Lukas Bach

Reputation: 3909

Even though this question is a bit older, I want to present a rather short solution to the problem.

const recursivelyGetKeys = obj => Object.keys(obj).map(key => typeof obj[key] === 'object' 
? [...recursivelyGetKeys(obj[key]), key] : [key]).reduce((p, c) => [...p, ...c], [])

This function will return all keys in the object, so a call to the array arr with

const arr = {
  brand: {
    group: {
       subGroup: {
          items: []
       },
       otherSub: {
          items: []
       }
     }
   }
 }

will output:

const keys = recursivelyGetKeys(arr) // = ["items", "subGroup", "items", "otherSub", "group", "brand"]

Now to find the intersection set of this and find = ['brand', 'group', 'newGroup', 'newSubGroup'], do:

const found = keys.filter(key => find.some(val === key))
const missing = keys.filter(key => find.every(val !== key))

Upvotes: 0

Lucky Soni
Lucky Soni

Reputation: 6878

    var toFind = ['brand', 'group', 'newGroup', 'newSubGroup'],
        found = [];

    var o = {
        brand: {
            group: {
                subGroup: {
                    items: []
                },
                otherSub: {
                    items: []
                }
            }
        }
    }

    //called with every property and its value
    function process(key,value) {
        var i = toFind.indexOf(key);
        if(i !== -1){
          found.push(key);
          toFind.splice(i, 1);
        }
    }
    
    function traverse(o,func) {
        if(!toFind.length) return;
        for (var i in o) {
            func.apply(this,[i,o[i]]);  
            if (o[i] !== null && typeof(o[i])=="object") {
                //going one step down in the object tree!!
                traverse(o[i],func);
            }
        }
    }
    
    traverse(o,process);

    console.log(found); // present
    console.log(toFind); // absent

Traverse method taken from https://stackoverflow.com/a/722732/1335165

Upvotes: 0

Rick
Rick

Reputation: 1055

To keep things clean and readable you can use "for in", inside a nested function for your recursion.

function recur(obj) {
  let preMiss = {
    present: [],
    missing: []
  }
  let root = traverse => {
    for (let key in traverse) {
      if (Array.isArray(traverse[key].items)) {
        preMiss.missing.push(key);
      }
      if (typeof traverse[key] === 'object' && !Array.isArray(traverse[key].items)) {
        preMiss.present.push(key);
        root(traverse[key])
      }
    }
  }
  root(obj);
  return preMiss;
}

const object = {
  brand: {
    group: {
      subGroup: {
        items: []
      },
      otherSub: {
        items: []
      }
    }
  }
}


console.log(Object.entries(recur(object)));

Upvotes: 0

Nenad Vracar
Nenad Vracar

Reputation: 122047

You can create recursive function using for...in loop inside another function and return object as result..

var obj = {"brand":{"group":{"subGroup":{"items":[]},"otherSub":{"items":[]}}}}
var keys =  ['brand', 'group', 'newGroup', 'newSubGroup'] ;

function findKeys(data, keys) {
  keys = keys.slice();
  
  function findPresent(data, keys) {
    var result = []
    
    for(var i in data) {
      if(typeof data[i] == 'object') result.push(...findPresent(data[i], keys))
      var index = keys.indexOf(i);
      if(index != -1) result.push(...keys.splice(index, 1))
    }
    
    return result
  }
  
  return {present: findPresent(data, keys), missing: keys}
}

console.log(findKeys(obj, keys))

Upvotes: 0

sauntimo
sauntimo

Reputation: 1591

I wrote a function to recursively get unique keys from a nested object, then filtered the array of all the keys you mentioned checking which were present in the result of my function.

var thisObject = {
	brand: {
  		group: {
    		subGroup: {
       			items: []
    		},
    		otherSub: {
       			items: []
    		}
        }
  	}
};

var arr_full =  ['brand', 'group', 'newGroup', 'newSubGroup'] ;

var key_array = [];

function addToKeyArray( key_array, object ){
	
    for( var key in object ){
    
    	// only get unique keys
    	if( key_array.indexOf( key ) === -1 ){
        	key_array.push( key );
        }
    
    	// concat the result of calling this function recurrsively on object[key]
    	key_array.concat( addToKeyArray( key_array, object[key] ) );	
    }
    
    return key_array;
}


var test = addToKeyArray( [], thisObject );

var missing = arr_full.filter( function( el ) {
  return test.indexOf( el ) < 0;
});

console.log( test );
console.log( missing )

Upvotes: 0

Arthur
Arthur

Reputation: 5148

You can use this function made for you ;)

var getAttrs = function(obj) {
  return [].concat.apply([], Object.keys(obj).map(function (key) { 
    var results = [key]
    if (typeof obj[key] === 'object') {
      Array.prototype.push.apply(results, getAttrs(obj[key]))
    }
    return results
  }))
}

It return the list of properties and children properties.

getAttrs({brand: {
  group: {
    subGroup: {
       items: []
    },
    otherSub: {
       items: []
    }
  }
}})

> ["brand", "group", "subGroup", "items", "otherSub", "items"]

And you can use it like so:

var lookingFor = ['brand', 'group', 'newGroup', 'newSubGroup']
var existings = getAttrs(obj)

var missings = []
var presents = []

lookingFor.forEach(attr => {
  if (existings.indexOf(attr) === -1) {
    missings.push(attr)
  } else { 
    presents.push(attr)
  }
})

Upvotes: 2

pabombs
pabombs

Reputation: 1470

Here's a pretty sketchy way that works.

const find = (keys, obj) => {
  const string = JSON.stringify(obj);
  return keys.reduce(({ present, missing }, key) => {
    const match = string.match(new RegExp(`"${key}":`));
    if (match) {
      present.push(key);
    } else {
      missing.push(key);
    }
    return { present, missing };
  }, { present: [], missing: [] });
}

Upvotes: 3

Related Questions