Skip to content

Instantly share code, notes, and snippets.

@creationix
Last active August 9, 2019 14:22
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save creationix/7473758 to your computer and use it in GitHub Desktop.
Save creationix/7473758 to your computer and use it in GitHub Desktop.
Key extractor that supports nested objects, nested arrays, duplicate keys, and cycles, returning an array of the unique keys.
// All of these should return the keys "a", "b", "c"
var inputs = [
{a:1,b:2,c:3}, // Simple object
{a:{b:2,c:3}}, // Simple object with nesting
{a:{a:{b:2,c:3}}}, // Repeated key hiding nesting
{a:[{b:2,c:3}]}, // keys behind array
];
inputs.push(inputs); // reference cycle and array at top
function getKeys(obj) {
var all = {};
var seen = [];
checkValue(obj);
return Object.keys(all);
function checkValue(value) {
if (Array.isArray(value)) return checkArray(value);
if (value instanceof Object) return checkObject(value);
}
function checkArray(array) {
if (seen.indexOf(array) >= 0) return;
seen.push(array);
for (var i = 0, l = array.length; i < l; i++) {
checkValue(array[i]);
}
}
function checkObject(obj) {
if (seen.indexOf(obj) >= 0) return;
seen.push(obj);
var keys = Object.keys(obj);
for (var i = 0, l = keys.length; i < l; i++) {
var key = keys[i];
all[key] = true;
checkValue(obj[key]);
}
}
}
var result = inputs.map(getKeys);
console.log(result);
// All of these should return the keys "a", "b", "c"
var inputs = [
{a:1,b:2,c:3}, // Simple object
{a:{b:2,c:3}}, // Simple object with nesting
{a:{a:{b:2,c:3}}}, // Repeated key hiding nesting
{a:[{b:2,c:3}]}, // keys behind array
];
inputs.push(inputs); // reference cycle and array at top
function getKeys(obj) {
var all = {};
var seen = [];
checkValue(obj, all, seen);
return Object.keys(all);
}
function checkValue(value, all, seen) {
if (Array.isArray(value)) return checkArray(value, all, seen);
if (value instanceof Object) return checkObject(value, all, seen);
}
function checkArray(array, all, seen) {
if (seen.indexOf(array) >= 0) return;
seen.push(array);
for (var i = 0, l = array.length; i < l; i++) {
checkValue(array[i], all, seen);
}
}
function checkObject(obj, all, seen) {
if (seen.indexOf(obj) >= 0) return;
seen.push(obj);
var keys = Object.keys(obj);
for (var i = 0, l = keys.length; i < l; i++) {
var key = keys[i];
all[key] = true;
checkValue(obj[key], all, seen);
}
}
var result = inputs.map(getKeys);
console.log(result);
@creationix
Copy link
Author

Output as tested in node.js

[ [ 'a', 'b', 'c' ],
  [ 'a', 'b', 'c' ],
  [ 'a', 'b', 'c' ],
  [ 'a', 'b', 'c' ],
  [ 'a', 'b', 'c' ] ]

@creationix
Copy link
Author

I prefer the closure style, but knowing what I know about how V8 optimized functions, the one with explicit state passing might be faster. Benchmark both to find out.

@creationix
Copy link
Author

Turns out the closure version is only slightly slower. I'd prefer it since it's much cleaner code to me. http://jsperf.com/get-keys-comprehensive

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment