Recursively flatten a deeply-nested mix of objects and arrays

I'm trying to flatten a data object that contains mixed content (JavaScript within a React application). I just need the keys of every parent and child in the object; I don't necessarily need the values (though having them present wouldn't be a deal-breaker).

I've searched for over a week to find a solution that would fit my use case, but everything I've tried has fallen short, including vanilla JavaScript, Lodash, Underscore, and flat (NPM package).

In every case, I either get a shorter list than I expect (because I'm only getting the parents), or I get a fully-flattened object with dot notation-delimited objects, which is useless to me.

I know there are lots of questions & answers pertaining to this topic, but nothing I've seen matches my use case, and I can't seem to wrap my head around the problem.

Here is a sample of my data structure:

const sampleData = {
  coyotes: '',
  armadillos: false,
  wombats: [''],
  geckos: 0,
  dogs: {
    beagle: '',
    bulldog: ''
  },
  wolves: [
    {
      type: '',
      color: '',
      range: '',
      status: {
        endangered: true,
        protected: false
      }
    }
  ],
  cats: {}
}

Here is what I (ideally) want back:

result = {coyotes, armadillos, wombats, geckos, dogs, beagle, bulldog, wolves, type, color, range, status, endangered, protected, cats}

Here is the way I'd like to call the flattening method:

flattenData(sampleData)

Here's what I have so far:

const flattenData = (obj) =>
      Object.assign(
        {},
        ...(function _flatten (o) {
          return [].concat(...Object.keys(o)
            .map(k =>
              typeof o[k] === 'object' ? _flatten(o[k]) : (Number([k]) !== 0 && { [k]: o[k] })
            )
          )
        }(obj))
      )

...which produces:

{
    "coyotes": "",
    "armadillos": false,
    "geckos": 0,
    "beagle": "",
    "bulldog": "",
    "type": "",
    "color": "",
    "range": "",
    "endangered": true,
    "protected": false
}

As you'll note, some parents are missing (wombats, dogs, wolves and status), and an empty object is missing (cats).


Solution 1:

This isn't the prettiest code I've ever wrote. Unfortunately it does get everything

const flattenData = (data) => {
  let results = [];
  const recursive = (data) => {
    Object.keys(data)
      .map(function (key) {
        results.push(key);
        recursive(data[key]);
        return data[key]; // This doesn't actually do anything...
      });
  }
  recursive(data);
  return results;
};

const value = flattenData(sampleData);