user3773571
user3773571

Reputation:

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?

Upvotes: 19

Views: 36067

Answers (8)

vincent
vincent

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

crystal
crystal

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

abdul vahith
abdul vahith

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

Mulan
Mulan

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

Olian04
Olian04

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

Nina Scholz
Nina Scholz

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

Nenad Vracar
Nenad Vracar

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

Robbie Milejczak
Robbie Milejczak

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

Related Questions