Recursively find data in array of objects On this script I look up properties in an array of objects. There is an original array of strings list to get the objects that match the name property, then I look up their respective line managers, reports recursively but only up to the rank 2, finally I merge their entries with the manager ones.
I was wondering if there is a more efficient way to achieve this result as I am having to make an object to index the managers that have already been checked, otherwise I get duplicates when two people have the same manager. Also having to specify the rank limit twice.


const data = [
  {
    name: 'rebecca',
    reports: '',
    rank: 1
  },
  {
    name: 'will',
    reports: 'rebecca',
    rank: 2
  },
  {
    name: 'jose',
    reports: 'will',
    rank: 3
  },
  {
    name: 'tomas',
    reports: 'jose',
    rank: 4,
  },
  {
    name: 'matt',
    reports: 'jose',
    rank: 5
  },{
    name: 'alison',
    reports: 'john',
    rank: 5
  }
]

// Initial list of names
const list = ['tomas', 'matt']

// Get the people on the list first
const filterList = data.filter(({ name }) =&gt; list.includes(name))

// Helper object to account for managers already checked and avoid duplicates
const managers = {}

// Find missing managers recursively
const findManager = (manager) =&gt; {
  const next = data.find(({ name }) =&gt; name === manager)
  managers[next.name] = true
  return next.rank &gt; 2 ? [next, ...findManager(next.reports)] : [next]
}

// Get the line managers for the filterList array
const missingManagers = []
for (const { reports, rank } of filterList) {
  if (!list.includes(reports) &amp;&amp; rank &gt; 2 &amp;&amp; !managers[reports] ) {
    missingManagers.push(...findManager(reports))
  }
}

const result = [...missingManagers, ...filterList]

console.log(result)
.as-console-wrapper { max-height: 100% !important; top: 0; }
From a medium review;

This code belongs in a well named function
I always forget about destructuring in a filter, very nice
Mapping to an object automatically removes dupes, something I (ab)use in my counter
const findManager = (manager)  should probably be const findManager = (employee)
const findManager = (employee)  should probably be const findLineManagers = (employee)
From there, next should probably be manager , it is definitely not 'next' but more 'actual' ;)
You are not using semicolons, unless you understand all nuances, you should use them
It's more idiomatic in recursive programming to first check for the exit condition than then to check at the end if you should recurse or not

I struggled with this counter-proposal. I imagine in a codebase I maintain I would want my data structure to be linked. So this function gets a linked version of the data and then gets what is needed in what I think is an easier manner. YMMV.


// Initial list of names

function indexPeopleData(peopleData){
  //Index by name
  const indexedPeople = peopleData.reduce((out,person) =&gt; (out[person.name] = person, out), {});
  //Make reports point to a people object
  peopleData.forEach(person =&gt; person.reports = indexedPeople[person.reports]);
  return indexedPeople;
}

function getLineManagers(peopleData, names){
  const indexedPeople = indexPeopleData(peopleData);
  //Return list of employees and their managers
  let out = {}, name;
  while(name = names.pop()){
    out[name] = indexedPeople[name];
    if(out[name].reports.rank &gt; 1){
      names.push(out[name].reports.name);
    }
  }
  return Object.values(out);
}

var data = [
  {
    name: 'rebecca',
    reports: '',
    rank: 1
  },
  {
    name: 'will',
    reports: 'rebecca',
    rank: 2
  },
  {
    name: 'jose',
    reports: 'will',
    rank: 3
  },
  {
    name: 'tomas',
    reports: 'jose',
    rank: 4,
  },
  {
    name: 'matt',
    reports: 'jose',
    rank: 5
  },{
    name: 'alison',
    reports: 'john',
    rank: 5
  }
];

console.log(getLineManagers(data, ['tomas', 'matt']));