81

Situation: I have a large object containing multiple sub and sub-sub objects, with properties containing multiple datatypes. For our purposes, this object looks something like this:

var object = {
    aProperty: {
        aSetting1: 1,
        aSetting2: 2,
        aSetting3: 3,
        aSetting4: 4,
        aSetting5: 5
    },
    bProperty: {
        bSetting1: {
            bPropertySubSetting : true
        },
        bSetting2: "bString"
    },
    cProperty: {
        cSetting: "cString"
    }
}

I need to loop through this object and build a list of the keys that shows the hierarchy, so the list ends up looking like this:

aProperty.aSetting1
aProperty.aSetting2
aProperty.aSetting3
aProperty.aSetting4
aProperty.aSetting5
bProperty.bSetting1.bPropertySubSetting
bProperty.bSetting2
cProperty.cSetting

I've got this function, which does loop through the object and spit out the keys, but not hierarchically:

function iterate(obj) {
    for (var property in obj) {
        if (obj.hasOwnProperty(property)) {
            if (typeof obj[property] == "object") {
                iterate(obj[property]);
            }
            else {
                console.log(property + "   " + obj[property]);
            }
        }
    }
}

Can somebody let me know how to do this? Here's a jsfiddle for you to mess with: http://jsfiddle.net/tbynA/

Jake
  • 4,014
  • 8
  • 36
  • 54
  • possible duplicate of [Object nested property access](http://stackoverflow.com/questions/13535226/object-nested-property-access) – Asad Saeeduddin Mar 28 '13 at 19:42
  • I don't think it is. That talks about accessing properties, all I need is to build a text-based list. – Jake Mar 28 '13 at 19:45
  • Oh. You're right then. The code in my question should suffice for building a text based list. Replace the line `parent[level + "." + p] = o[p];` with `yourArray.push(level + "." + p);` – Asad Saeeduddin Mar 28 '13 at 19:48

19 Answers19

119

I made a FIDDLE for you. I am storing a stack string and then output it, if the property is of primitive type:

function iterate(obj, stack) {
        for (var property in obj) {
            if (obj.hasOwnProperty(property)) {
                if (typeof obj[property] == "object") {
                    iterate(obj[property], stack + '.' + property);
                } else {
                    console.log(property + "   " + obj[property]);
                    $('#output').append($("<div/>").text(stack + '.' + property))
                }
            }
        }
    }

iterate(object, '')

Update: 17/01/2019

There used to be a different implementation, but it didn't work. See this answer for a prettier solution

JΛYDΞV
  • 8,532
  • 3
  • 51
  • 77
Artyom Neustroev
  • 8,627
  • 5
  • 33
  • 57
  • 25
    I want to have your code babies...if that's a thing. .child() maybe? Thanks. – Ryan Jun 02 '15 at 05:03
  • I'm using this but somehow my recursion falls into a loop and it causes overflow! when I console.log I can see that stuff are being repeated! probably some back referencing with the objects in window! any thoughts? – Saba Ahang Oct 15 '15 at 07:56
  • @SabaAhang try using `JSON.stringify` on your object. If it fails with [circular reference](http://stackoverflow.com/questions/10392293/stringify-javascript-object-with-circular-reference) error, you can try a [different solution](http://stackoverflow.com/questions/11616630/json-stringify-avoid-typeerror-converting-circular-structure-to-json) specific for that problem. – Artyom Neustroev Oct 15 '15 at 17:21
  • Here it is with a callback for each iteration and circular references are discarded.... https://gist.github.com/PAEz/57a99677e65f7d39a7a9 ...plus a couple of other functions I needed it for. – PAEz Nov 29 '15 at 04:19
  • It's doesnt works with nested objects :( bPropertySubSetting: { valid: true } – leocoder May 21 '18 at 18:04
  • @leocoder can you elaborate? The example code will output `bProperty.bSetting1.bPropertySubSetting.valid` - is that different from what you were expecting? – Artyom Neustroev May 23 '18 at 07:10
  • Well I'm not sure if I was wrong with my object structure, but check this example https://jsfiddle.net/138ngbfy/ – leocoder May 23 '18 at 17:23
  • @leocoder ugh you are right, seems like the function that prints the graph doesn't work properly with nesting :( I'll try to look into it and update – Artyom Neustroev May 28 '18 at 12:44
  • Just what I needed! Quick question. I'm new to TS. Why is the check `obj.hasOwnProperty(property)` made right after `for(var property in obj)`? Isn't `property` guaranteed to be in `obj`? – buttonsrtoys Dec 05 '20 at 13:17
  • 1
    @buttonsrtoys check out this explanation! https://stackoverflow.com/a/136411/2120289 – Artyom Neustroev Dec 09 '20 at 11:20
82

The solution from Artyom Neustroev does not work on complex objects, so here is a working solution based on his idea:

function propertiesToArray(obj) {
  const isObject = val =>
    val && typeof val === 'object' && !Array.isArray(val);

  const addDelimiter = (a, b) =>
    a ? `${a}.${b}` : b;

  const paths = (obj = {}, head = '') => {
    return Object.entries(obj)
      .reduce((product, [key, value]) => 
        {
          let fullPath = addDelimiter(head, key)
          return isObject(value) ?
            product.concat(paths(value, fullPath))
          : product.concat(fullPath)
        }, []);
  }

  return paths(obj);
}
  
const foo = {foo: {bar: {baz: undefined}, fub: 'goz', bag: {zar: {zaz: null}, raz: 3}}}
const result = propertiesToArray(foo)
console.log(result)

EDIT (2023/05/23):

4 different (complete) solutions with full descriptions are available on LeetCode: https://leetcode.com/problems/array-of-objects-to-matrix/editorial/?utm_campaign=PostD19&utm_medium=Post&utm_source=Post&gio_link_id=EoZk0Zy9

Matjaz
  • 1,301
  • 10
  • 7
  • 1
    This is gold! I don't know why this isn't voted higher. For some simple (but ugly) HTML printing, one can use: `return (isObject(value) ? product.concat(paths(value, fullPath)): product.concat(fullPath) ) + ': ' + obj[key] + '
    '`
    – Justin May 19 '20 at 10:11
  • Oh my god, this is a work of art! I just spent like 45 minutes working out how to do something similar and this worked perfectly. Nice job! – isshakes Feb 03 '21 at 19:59
  • 3
    NIce! only an observation. You should check for `null` in the `isObject` arrow function. since `typeof null = 'object'`. – Omar Omeiri May 26 '21 at 19:36
  • 2
    Worked well only were having some errors when there were null in object. So this one worked for me: return Object.entries(obj || {}) – LuckyLuke Jun 12 '21 at 14:35
  • 3
    For those who just want the value : product.concat(value) instead of : product.concat(fullPath) – Non404 Jul 21 '21 at 12:36
  • @OmarOmeiri but null is a falsy value so you'll not go to the second case in isObject function – Freestyle09 Aug 25 '22 at 10:11
19

You'll run into issues with this if the object has loop in its object graph, e.g something like:

var object = {
    aProperty: {
        aSetting1: 1
    },
};
object.ref = object;

In that case you might want to keep references of objects you've already walked through & exclude them from the iteration.

Also you can run into an issue if the object graph is too deep like:

var object = {
  a: { b: { c: { ... }} }
};

You'll get too many recursive calls error. Both can be avoided:

function iterate(obj) {
    var walked = [];
    var stack = [{obj: obj, stack: ''}];
    while(stack.length > 0)
    {
        var item = stack.pop();
        var obj = item.obj;
        for (var property in obj) {
            if (obj.hasOwnProperty(property)) {
                if (typeof obj[property] == "object") {
                  var alreadyFound = false;
                  for(var i = 0; i < walked.length; i++)
                  {
                    if (walked[i] === obj[property])
                    {
                      alreadyFound = true;
                      break;
                    }
                  }
                  if (!alreadyFound)
                  {
                    walked.push(obj[property]);
                    stack.push({obj: obj[property], stack: item.stack + '.' + property});
                  }
                }
                else
                {
                    console.log(item.stack + '.' + property + "=" + obj[property]);
                }
            }
        }
    }
}

iterate(object); 
Ondrej Svejdar
  • 21,349
  • 5
  • 54
  • 89
14

https://github.com/hughsk/flat

var flatten = require('flat')
flatten({
key1: {
    keyA: 'valueI'
},
key2: {
    keyB: 'valueII'
},
key3: { a: { b: { c: 2 } } }
})

// {
//   'key1.keyA': 'valueI',
//   'key2.keyB': 'valueII',
//   'key3.a.b.c': 2
// }

Just loop to get the indexes after.

Matthieu Drula
  • 201
  • 3
  • 11
9

You don't need recursion!

The following function function which will output the entries in the order of least deep to the most deep with the value of the key as a [key, value] array.

function deepEntries( obj ){
    'use-strict';
    var allkeys, curKey = '[', len = 0, i = -1, entryK;

    function formatKeys( entries ){
       entryK = entries.length;
       len += entries.length;
       while (entryK--)
         entries[entryK][0] = curKey+JSON.stringify(entries[entryK][0])+']';
       return entries;
    }
    allkeys = formatKeys( Object.entries(obj) );

    while (++i !== len)
        if (typeof allkeys[i][1] === 'object' && allkeys[i][1] !== null){
            curKey = allkeys[i][0] + '[';
            Array.prototype.push.apply(
                allkeys,
                formatKeys( Object.entries(allkeys[i][1]) )
            );
        }
    return allkeys;
}

Then, to output the kind of results you are looking for, just use this.

function stringifyEntries(allkeys){
    return allkeys.reduce(function(acc, x){
        return acc+((acc&&'\n')+x[0])
    }, '');
};

If your interested in the technical bits, then this is how it works. It works by getting the Object.entries of the obj object you passed and puts them in array allkeys. Then, going from the beggining of allkeys to the end, if it finds that one of allkeys entries value's is an object then it gets that entrie's key as curKey, and prefixes each of its own entries keys with curKey before it pushes that resulting array onto the end of allkeys. Then, it adds the number of entries added to allkeys to the target length so that it will also go over those newly added keys too.

For example, observe the following:

<script>
var object = {
    aProperty: {
        aSetting1: 1,
        aSetting2: 2,
        aSetting3: 3,
        aSetting4: 4,
        aSetting5: 5
    },
    bProperty: {
        bSetting1: {
            bPropertySubSetting : true
        },
        bSetting2: "bString"
    },
    cProperty: {
        cSetting: "cString"
    }
}
document.write(
    '<pre>' + stringifyEntries( deepEntries(object) ) + '</pre>'
);
function deepEntries( obj ){//debugger;
    'use-strict';
    var allkeys, curKey = '[', len = 0, i = -1, entryK;

    function formatKeys( entries ){
       entryK = entries.length;
       len += entries.length;
       while (entryK--)
         entries[entryK][0] = curKey+JSON.stringify(entries[entryK][0])+']';
       return entries;
    }
    allkeys = formatKeys( Object.entries(obj) );

    while (++i !== len)
        if (typeof allkeys[i][1] === 'object' && allkeys[i][1] !== null){
            curKey = allkeys[i][0] + '[';
            Array.prototype.push.apply(
                allkeys,
                formatKeys( Object.entries(allkeys[i][1]) )
            );
        }
    return allkeys;
}
function stringifyEntries(allkeys){
    return allkeys.reduce(function(acc, x){
        return acc+((acc&&'\n')+x[0])
    }, '');
};
</script>

Or, if you only want the properties, and not the objects that have properties, then you can filter then out like so:

deepEntries(object).filter(function(x){return typeof x[1] !== 'object'});

Example:

<script>
var object = {
    aProperty: {
        aSetting1: 1,
        aSetting2: 2,
        aSetting3: 3,
        aSetting4: 4,
        aSetting5: 5
    },
    bProperty: {
        bSetting1: {
            bPropertySubSetting : true
        },
        bSetting2: "bString"
    },
    cProperty: {
        cSetting: "cString"
    }
}
document.write('<pre>' + stringifyEntries(
    deepEntries(object).filter(function(x){
       return typeof x[1] !== 'object';
    })
) + '</pre>');
function deepEntries( obj ){//debugger;
    'use-strict';
    var allkeys, curKey = '[', len = 0, i = -1, entryK;

    function formatKeys( entries ){
       entryK = entries.length;
       len += entries.length;
       while (entryK--)
         entries[entryK][0] = curKey+JSON.stringify(entries[entryK][0])+']';
       return entries;
    }
    allkeys = formatKeys( Object.entries(obj) );

    while (++i !== len)
        if (typeof allkeys[i][1] === 'object' && allkeys[i][1] !== null){
            curKey = allkeys[i][0] + '[';
            Array.prototype.push.apply(
                allkeys,
                formatKeys( Object.entries(allkeys[i][1]) )
            );
        }
    return allkeys;
}
function stringifyEntries(allkeys){
    return allkeys.reduce(function(acc, x){
        return acc+((acc&&'\n')+x[0])
    }, '');
};
</script>

Browser Compatibility

The above solution will not work in IE, rather it will only work in Edge because it uses the Object.entries function. If you need IE9+ support, then simply add the following Object.entries polyfill to your code. If you, for some reason beyond me, actually do need IE6+ support, then you will also need an Object.keys and JSON.stringify polyfill (neither listed here, so find it somewhere else).

if (!Object.entries)
  Object.entries = function( obj ){
    var ownProps = Object.keys( obj ),
        i = ownProps.length,
        resArray = new Array(i); // preallocate the Array
    while (i--)
      resArray[i] = [ownProps[i], obj[ownProps[i]]];

    return resArray;
  };
Jack G
  • 4,553
  • 2
  • 41
  • 50
4

With a little help from lodash...

/**
 * For object (or array) `obj`, recursively search all keys
 * and generate unique paths for every key in the tree.
 * @param {Object} obj
 * @param {String} prev
 */
export const getUniqueKeyPaths = (obj, prev = '') => _.flatten(
  Object
  .entries(obj)
  .map(entry => {
    const [k, v] = entry
    if (v !== null && typeof v === 'object') {
      const newK = prev ? `${prev}.${k}` : `${k}`
      // Must include the prev and current k before going recursive so we don't lose keys whose values are arrays or objects
      return [newK, ...getUniqueKeyPaths(v, newK)]
    }
    return `${prev}.${k}`
  })
)
ZCaceres
  • 83
  • 4
3

This solution won't fail if there is a null value anywhere.

function recursiveKeys(obj) {
  const helper = (obj, prefix, acc) => {
    if ("" !== prefix) acc.push(prefix);
    if (typeof obj === "object" && obj !== null) {
      if (Array.isArray(obj)) {
        for (let k = 0; k < obj.length; k++) {
          helper(obj[k], prefix + "[" + k + "]", acc);
        }
      } else {
        const keys = Object.keys(obj);
        keys.forEach((k) => {
          helper(obj[k], prefix + "." + k, acc);
        });
      }
    }
    return acc;
  };
  return helper(obj, "", []);
}

Called like this

const obj = {
  name: "Sherlock Holmes",
  address: { street: "221B Baker Street", city: "London" },
  fruits: ["Orange", "Apple"],
};
recursiveKeys(obj);

it returns this

[
  ".name",
  ".address",
  ".address.street",
  ".address.city",
  ".fruits",
  ".fruits[0]",
  ".fruits[1]",
]
mikeyreilly
  • 6,523
  • 1
  • 26
  • 21
2

This version is packed in a function that accepts a custom delimiter, filter, and returns a flat dictionary:

function flatten(source, delimiter, filter) {
  var result = {}
  ;(function flat(obj, stack) {
    Object.keys(obj).forEach(function(k) {
      var s = stack.concat([k])
      var v = obj[k]
      if (filter && filter(k, v)) return
      if (typeof v === 'object') flat(v, s)
      else result[s.join(delimiter)] = v
    })
  })(source, [])
  return result
}
var obj = {
  a: 1,
  b: {
    c: 2
  }
}
flatten(obj)
// <- Object {a: 1, b.c: 2}
flatten(obj, '/')
// <- Object {a: 1, b/c: 2}
flatten(obj, '/', function(k, v) { return k.startsWith('a') })
// <- Object {b/c: 2}
Jaime Gómez
  • 6,961
  • 3
  • 40
  • 41
2

UPDATE: JUST USE JSON.stringify to print objects on screen!

All you need is this line:

document.body.innerHTML = '<pre>' + JSON.stringify(ObjectWithSubObjects, null, "\t") + '</pre>';

This is my older version of printing objects recursively on screen:

 var previousStack = '';
    var output = '';
    function objToString(obj, stack) {
        for (var property in obj) {
            var tab = '&nbsp;&nbsp;&nbsp;&nbsp;';
            if (obj.hasOwnProperty(property)) {
                if (typeof obj[property] === 'object' && typeof stack === 'undefined') {
                    config = objToString(obj[property], property);
                } else {
                    if (typeof stack !== 'undefined' && stack !== null && stack === previousStack) {
                        output = output.substring(0, output.length - 1);  // remove last }
                        output += tab + '<span>' + property + ': ' + obj[property] + '</span><br />'; // insert property
                        output += '}';   // add last } again
                    } else {
                        if (typeof stack !== 'undefined') {
                            output += stack + ': {  <br />' + tab;
                        }
                        output += '<span>' + property + ': ' + obj[property] + '</span><br />';
                        if (typeof stack !== 'undefined') {
                            output += '}';
                        }
                    }
                    previousStack = stack;
                }
            }
        }
        return output;
    }

Usage:

document.body.innerHTML = objToString(ObjectWithSubObjects);

Example output:

cache: false
position: fixed
effect: { 
    fade: false
    fall: true
}

Obviously this can be improved by adding comma's when needed and quotes from string values. But this was good enough for my case.

Floris
  • 2,727
  • 2
  • 27
  • 47
1

Solution to flatten properties and arrays as well.

Example input:

{
  obj1: {
    prop1: "value1",
    prop2: "value2"
  },
  arr1: [
    "value1",
    "value2"
  ]
}

Output:

"arr1[0]": "value1"
"arr1[1]": "value2"
"obj1.prop1": "value1"
"obj1.prop2": "value2"

Source code:

flatten(object, path = '', res = undefined) {
      if (!Array.isArray(res)) {
          res = [];
      }
      if (object !== null && typeof object === 'object') {
          if (Array.isArray(object)) {
              for (let i = 0; i < object.length; i++) {
                  this.flatten(object[i], path + '[' + i + ']', res)
              }
          } else {
              const keys = Object.keys(object)
              for (let i = 0; i < keys.length; i++) {
                  const key = keys[i]
                  this.flatten(object[key], path ? path + '.' + key : key, res)
              }
          }
      } else {
          if (path) {
              res[path] = object
          }
      }
      return res
  }
Sergey Gurin
  • 1,537
  • 15
  • 14
1

Here is a simple solution. This is a late answer but may be simple one-

const data = {
  city: 'foo',
  year: 2020,
  person: {
    name: {
      firstName: 'john',
      lastName: 'doe'
    },
    age: 20,
    type: {
      a: 2,
      b: 3,
      c: {
        d: 4,
        e: 5
      }
    }
  },
}

function getKey(obj, res = [], parent = '') {
  const keys = Object.keys(obj);
  
  /** Loop throw the object keys and check if there is any object there */
  keys.forEach(key => {
    if (typeof obj[key] !== 'object') {
      // Generate the heirarchy
      parent ? res.push(`${parent}.${key}`) : res.push(key);
    } else {
      // If object found then recursively call the function with updpated parent
      let newParent = parent ? `${parent}.${key}` : key;
      getKey(obj[key], res, newParent);
    }
    
  });
}

const result = [];

getKey(data, result, '');

console.log(result);
.as-console-wrapper{min-height: 100%!important; top: 0}
Sajeeb Ahamed
  • 6,070
  • 2
  • 21
  • 30
1

You can use a recursive Object.keys to achieve that.

var keys = []

const findKeys = (object, prevKey = '') => {
  Object.keys(object).forEach((key) => {
    const nestedKey = prevKey === '' ? key : `${prevKey}.${key}`

    if (typeof object[key] !== 'object') return keys.push(nestedKey)

    findKeys(object[key], nestedKey)
  })
}

findKeys(object)

console.log(keys)

Which results in this array

[
  "aProperty.aSetting1",
  "aProperty.aSetting2",
  "aProperty.aSetting3",
  "aProperty.aSetting4",
  "aProperty.aSetting5",
  "bProperty.bSetting1.bPropertySubSetting",
  "bProperty.bSetting2",
  "cProperty.cSetting"
]

To test, you can provide your object:

object = {
  aProperty: {
    aSetting1: 1,
    aSetting2: 2,
    aSetting3: 3,
    aSetting4: 4,
    aSetting5: 5
  },
  bProperty: {
    bSetting1: {
      bPropertySubSetting: true
    },
    bSetting2: "bString"
  },
  cProperty: {
    cSetting: "cString"
  }
}
Victor
  • 1,904
  • 18
  • 18
0

Suppose that you have a JSON object like:

var example = {
    "prop1": "value1",
    "prop2": [ "value2_0", "value2_1"],
    "prop3": {
         "prop3_1": "value3_1"
    }
}

The wrong way to iterate through its 'properties':

function recursivelyIterateProperties(jsonObject) {
    for (var prop in Object.keys(jsonObject)) {
        console.log(prop);
        recursivelyIterateProperties(jsonObject[prop]);
    }
}

You might be surprised of seeing the console logging 0, 1, etc. when iterating through the properties of prop1 and prop2 and of prop3_1. Those objects are sequences, and the indexes of a sequence are properties of that object in Javascript.

A better way to recursively iterate through a JSON object properties would be to first check if that object is a sequence or not:

function recursivelyIterateProperties(jsonObject) {
    for (var prop in Object.keys(jsonObject)) {
        console.log(prop);
        if (!(typeof(jsonObject[prop]) === 'string')
            && !(jsonObject[prop] instanceof Array)) {
                recursivelyIterateProperties(jsonObject[prop]);

            }
     }
}

If you want to find properties inside of objects in arrays, then do the following:

function recursivelyIterateProperties(jsonObject) {

    if (jsonObject instanceof Array) {
        for (var i = 0; i < jsonObject.length; ++i) {
            recursivelyIterateProperties(jsonObject[i])
        }
    }
    else if (typeof(jsonObject) === 'object') {
        for (var prop in Object.keys(jsonObject)) {
            console.log(prop);
            if (!(typeof(jsonObject[prop]) === 'string')) {
                recursivelyIterateProperties(jsonObject[prop]);
            }
        }
    }
}
Jadiel de Armas
  • 8,405
  • 7
  • 46
  • 62
0

An improved solution with filtering possibilities. This result is more convenient as you can refer any object property directly with array paths like:

["aProperty.aSetting1", "aProperty.aSetting2", "aProperty.aSetting3", "aProperty.aSetting4", "aProperty.aSetting5", "bProperty.bSetting1.bPropertySubSetting", "bProperty.bSetting2", "cProperty.cSetting"]

 /**
 * Recursively searches for properties in a given object. 
 * Ignores possible prototype endless enclosures. 
 * Can list either all properties or filtered by key name.
 *
 * @param {Object} object Object with properties.
 * @param {String} key Property key name to search for. Empty string to 
 *                     get all properties list .
 * @returns {String} Paths to properties from object root.
 */
function getPropertiesByKey(object, key) {

  var paths = [
  ];

  iterate(
    object,
    "");

  return paths;

  /**
   * Single object iteration. Accumulates to an outer 'paths' array.
   */
  function iterate(object, path) {
    var chainedPath;

    for (var property in object) {
      if (object.hasOwnProperty(property)) {

        chainedPath =
          path.length > 0 ?
          path + "." + property :
          path + property;

        if (typeof object[property] == "object") {

          iterate(
            object[property],
            chainedPath,
            chainedPath);
        } else if (
          property === key ||
          key.length === 0) {

          paths.push(
            chainedPath);
        }
      }
    }

    return paths;
  }
}
Zon
  • 18,610
  • 7
  • 91
  • 99
0

I'll provide a solution too, using recursion. Commented lines to clarify things.

It works well for its purpose right now.

// works only if the value is a dictionary or something specified below, and adds all keys in nested objects and outputs them

const example = {
  city: "foo",
  year: 2020,
  person: {
    name: "foo",
    age: 20,
    deeper: {
      even_deeper: {
        key: "value", 
        arr: [1, 2, {
          a: 1,
          b: 2
        }]
      }
    }
  },
};

var flat  =  [];    // store keys
var depth =  0;     // depth, used later
var path  =  "obj"; // base path to be added onto, specified using the second parameter of flatKeys 

let flatKeys = (t, name) => {
  path = name ? name : path;  // if specified, set the path 
  for (const k in t) {
    const v = t[k];
    let type = typeof v;      // store the type value's type
    switch (type) {  
      case "string":          // these are the specified cases for which a key will be added,
      case "number":          // specify more if you want
      case "array" :
        flat.push(path + "." + k);  // add the complete path to the array
        break;
      case "object":
        flat.push(path + "." + k)
        path += "." + k;
        flatKeys(v);
        break;
    }
  }
  return flat;
};

let flattened = flatKeys(example, "example"); // the second argument is what the root path should be (for convenience)
console.log(flattened, "keys: " + flattened.length);
Ethicist
  • 791
  • 2
  • 7
  • 23
0

A simple path global variable across each recursive call does the trick for me !

var object = {
  aProperty: {
    aSetting1: 1,
    aSetting2: 2,
    aSetting3: 3,
    aSetting4: 4,
    aSetting5: 5
  },
  bProperty: {
    bSetting1: {
      bPropertySubSetting: true
    },
    bSetting2: "bString"
  },
  cProperty: {
    cSetting: "cString"
  }
}

function iterate(obj, path = []) {
  for (var property in obj) {
    if (obj.hasOwnProperty(property)) {
      if (typeof obj[property] == "object") {
        let curpath = [...path, property];
        iterate(obj[property], curpath);
      } else {
        console.log(path.join('.') + '.' + property + "   " + obj[property]);
        $('#output').append($("<div/>").text(path.join('.') + '.' + property))
      }
    }
  }
}

iterate(object);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js"></script>
<div id='output'></div>
0

matjaz's answer was almost perfect for me, except I have arrays in my Json so I did this:

function propertiesToArray(obj) {
  const isObject = val =>
    val && typeof val === 'object' && !Array.isArray(val);

  const addDelimiter = (a, b) =>
    a ? `${a}.${b}` : b;

  const paths = (obj = {}, head = '') => {
    return Object.entries(obj)
      .reduce((product, [key, value]) => 
        {
          let fullPath = addDelimiter(head, key)
          return isObject(value) ?
            product.concat(paths(value, fullPath))
            : Array.isArray(value) ? 
                product.concat(addDelimiter(key, propertiesToArray(value)))
              : product.concat(fullPath)
        }, []);
  }

  return paths(obj);
}

const foo = {foo: {bar: {baz: undefined}, fub: 'goz', arr: [{'aro1':'bla1','bro2':'bla2','cro3':'bla3'}], bag: {zar: {zaz: null}, raz: 3}}}
const result = propertiesToArray(foo)
console.log(result)
Gil Cohen
  • 836
  • 7
  • 12
0

Some may find this solution more readable.

This accepts a JSON object and returns an array of all the nested properties.

//Recursively iterates over all properties in object to get the keys
function get_keys(obj) { 
    let keys = [];
    const property_names = Object.keys(obj);
    for (const property of property_names) { 
        keys.push(property);
        if(typeof(obj[property]) === 'object') {
            keys = keys.concat(get_keys(obj[property]));
        }
    }
    return keys; 
}
tommmm
  • 182
  • 1
  • 4
-1

This function can handle objects containing both objects and arrays of objects. Result will be one line per each single item of the object, representing its full path in the structure.

Tested with http://haya2now.jp/data/data.json

Example result: geometry[6].obs[5].hayabusa2.delay_from

function iterate(obj, stack, prevType) {
    for (var property in obj) {
        if ( Array.isArray(obj[property]) ) {
            //console.log(property , "(L="  + obj[property].length + ") is an array  with parent ", prevType, stack);
            iterate(obj[property], stack  + property , "array");
        } else {
            if ((typeof obj[property] != "string")  && (typeof obj[property] != "number"))  {
                if(prevType == "array") {
                    //console.log(stack + "["  + property + "] is an object, item of " , prevType, stack);
                    iterate(obj[property], stack + "["  +property + "]." , "object");
                } else {
                    //console.log(stack +    property  , "is " , typeof obj[property] , " with parent ", prevType, stack );
                    iterate(obj[property], stack  + property + ".", "object");
                }   
            } else {
                if(prevType == "array") {
                    console.log(stack + "["  + property + "] =  "+  obj[property]);

                } else {
                    console.log(stack +    property  , " =  " ,  obj[property] );                       
                }   
            }
        }



    }
}

iterate(object, '', "File")
console.log(object);
jumpjack
  • 841
  • 1
  • 11
  • 17