简体   繁体   中英

How to get all keys with values from nested objects

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).

Example A

Input

{
   "check_id":12345,
   "check_name":"Name of HTTP check",
   "check_type":"HTTP"
}

Expected output

[
  "check_id",
  "check_name",
  "check_type"
]

Object.keys would work for flat cases like this, but not for nested cases:

Example B

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,
      }
   }
}

Expected output

[
  "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.

The question

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?

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);

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"
]

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)

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; }

 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);

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'
]

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM