Reputation:
I'm looking for something kind of like Object.keys
but that works for potentially nested objects. It also shouldn't include keys that have object/array values (it should only include keys with immediate string/number/boolean values).
{
"check_id":12345,
"check_name":"Name of HTTP check",
"check_type":"HTTP"
}
[
"check_id",
"check_name",
"check_type"
]
Object.keys
would work for flat cases like this, but not for nested cases:
{
"check_id":12345,
"check_name":"Name of HTTP check",
"check_type":"HTTP",
"tags":[
"example_tag"
],
"check_params":{
"basic_auth":false,
"params":[
"size"
],
"encryption": {
"enabled": true,
}
}
}
[
"check_id",
"check_name",
"check_type",
"check_params.basic_auth",
"check_params.encryption.enabled"
]
Note that this does not include tags
, check_params
, check_params.params
, or check_params.encryption
since these values are arrays/objects.
Is there a library that does this? How would you implement it so that it can work with any object, large and nested, or small?
Upvotes: 19
Views: 36067
Reputation: 2171
Since this was recently revived, here is a solution using object-scan. The library is very flexible and should allow to easily get the desired result.
Settings are all explained with examples in the link above, but joined=true
makes the keys come back as strings, filterFn
only returns keys that have simple targets and afterFn
flips the result since the library traverses depth first. Array keys are excluded as targets by using **.*
as the selector.
.as-console-wrapper {max-height: 100% !important; top: 0}
<script type="module">
import objectScan from 'https://cdn.jsdelivr.net/npm/[email protected]/lib/index.min.js';
const obj = { check_id: 12345, check_name: 'Name of HTTP check', check_type: 'HTTP', tags: ['example_tag'], check_params: { basic_auth: false, params: ['size'], encryption: { enabled: true } } };
const r = objectScan(['**.*'], {
joined: true,
filterFn: ({ isLeaf }) => isLeaf,
afterFn: ({ result }) => result.reverse()
})(obj);
console.log(r);
/* => [
'check_id',
'check_name',
'check_type',
'check_params.basic_auth',
'check_params.encryption.enabled'
] */
</script>
Disclaimer: I'm the author of object-scan
Upvotes: 1
Reputation: 241
Further enhanced above recommendation to return all keys including array.
const keyify = (obj, prefix = '') =>
Object.keys(obj).reduce((res, el) => {
if( Array.isArray(obj[el]) ) {
return [...res,`${el}: ${obj[el].toString()}`];
} else if( typeof obj[el] === 'object' && obj[el] !== null ) {
return [...res,...keyify(obj[el],`${prefix}${el}.`)];
}
return [...res,`${prefix}${el}: ${obj[el]}`];
}, []);
const input = {
"check_id":12345,
"check_name":"Name of HTTP check",
"check_type":"HTTP",
"tags":[
"example_tag"
],
"check_params":{
"basic_auth":false,
"params":[
"size"
],
"encryption": {
"enabled": true,
"testNull": null,
}
}
};
const output = keyify(input);
console.log(output);
Expected output:
[
'check_id: 12345',
'check_name: Name of HTTP check',
'check_type: HTTP',
'tags: example_tag',
'check_params.basic_auth: false',
'params: size',
'check_params.encryption.enabled: true',
'check_params.encryption.testNull: null'
]
Upvotes: 0
Reputation: 16
var json = {
id: '1234',
test: 'terst',
user : {
name: '',
details: {
address: {
add2: {
prim: "",
sec: ""
},
add1: '',
}
}
},
salary: {
cur: 1234,
net: 89797
},
age: 12
}
let arr = [];
let initialObj = {};
function getKeys(obj, parentK=''){
initialObj = arr.length === 0 ? obj: initialObj;
const entries = Object.entries(obj);
for(let i=0; i<entries.length; i++) {
const key = entries[i][0];
const val = entries[i][1];
const isRootElement = initialObj.hasOwnProperty(key);
parentK = isRootElement ? key: parentK+'.'+key;
arr.push(parentK)
if(typeof val === 'object' && val!==null && !Array.isArray(val)){
getKeys(val, parentK);
}
}
}
getKeys(json)
console.log('arr final---', arr);
Upvotes: 0
Reputation: 135197
A generator makes quick work of this kind of problem -
function* deepKeys (t, pre = [])
{ if (Array.isArray(t))
return
else if (Object(t) === t)
for (const [k, v] of Object.entries(t))
yield* deepKeys(v, [...pre, k])
else
yield pre.join(".")
}
const input =
{check_id:12345,check_name:"Name of HTTP check",check_type:"HTTP",tags:["example_tag"],check_params:{basic_auth:false,params:["size"],encryption:{enabled:true,testNull:null,}}}
console.log(Array.from(deepKeys(input)))
[ "check_id"
, "check_name"
, "check_type"
, "check_params.basic_auth"
, "check_params.encryption.enabled"
, "check_params.encryption.testNull"
]
Or a pure functional expression which eagerly computes all keys -
const deepKeys = (t, pre = []) =>
Array.isArray(t)
? []
: Object(t) === t
? Object
.entries(t)
.flatMap(([k, v]) => deepKeys(v, [...pre, k]))
: pre.join(".")
const input =
{check_id:12345,check_name:"Name of HTTP check",check_type:"HTTP",tags:["example_tag"],check_params:{basic_auth:false,params:["size"],encryption:{enabled:true,testNull:null,}}}
console.log(deepKeys(input))
[ "check_id"
, "check_name"
, "check_type"
, "check_params.basic_auth"
, "check_params.encryption.enabled"
, "check_params.encryption.testNull"
]
Upvotes: 8
Reputation: 6872
You could use reduce like this:
const keyify = (obj, prefix = '') =>
Object.keys(obj).reduce((res, el) => {
if( Array.isArray(obj[el]) ) {
return res;
} else if( typeof obj[el] === 'object' && obj[el] !== null ) {
return [...res, ...keyify(obj[el], prefix + el + '.')];
}
return [...res, prefix + el];
}, []);
const input = {
"check_id":12345,
"check_name":"Name of HTTP check",
"check_type":"HTTP",
"tags":[
"example_tag"
],
"check_params":{
"basic_auth":false,
"params":[
"size"
],
"encryption": {
"enabled": true,
"testNull": null,
}
}
};
const output = keyify(input);
console.log(output);
Edit1: For the general case where you want to include arrays.
const keyify = (obj, prefix = '') =>
Object.keys(obj).reduce((res, el) => {
if( typeof obj[el] === 'object' && obj[el] !== null ) {
return [...res, ...keyify(obj[el], prefix + el + '.')];
}
return [...res, prefix + el];
}, []);
const input = {
"check_id":12345,
"check_name":"Name of HTTP check",
"check_type":"HTTP",
"tags":[
"example_tag"
],
"nested": [
{ "foo": 0 },
{ "bar": 1 }
],
"check_params":{
"basic_auth":false,
"params":[
"size"
],
"encryption": {
"enabled": true,
"testNull": null,
}
}
};
const output = keyify(input);
console.log(output);
Upvotes: 42
Reputation: 386520
You could check the keys and iterate otherwise push the path to the result set.
function getKeys(object) {
function iter(o, p) {
if (Array.isArray(o)) { return; }
if (o && typeof o === 'object') {
var keys = Object.keys(o);
if (keys.length) {
keys.forEach(function (k) { iter(o[k], p.concat(k)); });
}
return;
}
result.push(p.join('.'));
}
var result = [];
iter(object, []);
return result;
}
var object = { check_id: 12345, check_name: "Name of HTTP check", check_type: "HTTP", tags: ["example_tag"], check_params: { basic_auth: false, params: ["size"], encryption: { enabled: true } } };
console.log(getKeys(object));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Upvotes: 2
Reputation: 122027
You can use for...in
and create recursive function.
var obj = {"check_id":12345,"check_name":"Name of HTTP check","check_type":"HTTP","tags":["example_tag"],"check_params":{"basic_auth":false,"params":["size",{"a":"b"}],"encryption":{"enabled":true}}}
var keys = []
function getKeys(data, k = '') {
for (var i in data) {
var rest = k.length ? '.' + i : i
if (typeof data[i] == 'object') {
if (!Array.isArray(data[i])) {
getKeys(data[i], k + rest)
}
} else keys.push(k + rest)
}
}
getKeys(obj)
console.log(keys)
Upvotes: 1
Reputation: 5770
Is this what you mean?
http://jsfiddle.net/robbiemilejczak/hfe12brb/1/
I couldn't do it with vanilla JS, and this is a pretty hacky solution that relies on lodash. Basically leverages lodashs _.forIn
and _.isArray
functions to iterate over an object. Also this will only go 1 layer deep, so objects inside of nested objects will be ignored. It does produce your expected output though, so I'd say it's a decent starting point.
Upvotes: -1