简体   繁体   中英

Get values from immediate parent for any given child in array of nested JSON objects?

Here is a sample of my JSON.

const data = [{
   "employees": [ 
      {         
         "employee": [
            {
               "name": "Jon",
               "surname": "Smith",
               "leaveRequest": [
                  {
                     "id": "3000",
                     "approver": "Terry"
                  }
               ]
            }],
      },
      {
         "employee": [
            {
               "name": "Mike",
               "surname": "Jones",
               "leaveRequest": [
                  {
                     "id": "1700",
                     "approver": "Mary"
                  }
               ]
            },
         ]
      }
   ]
}];

I want to be able to search by id across all leaveRequests and get the employee's name and surname.

If I pass id "3000" I would like to be able to get an object ["name": "Jon", "surname": "Smith"]

I've tried implementing the solution from: How to get immediate parent Id of the child id in array of nested json Object?

This is what I've tried:

const findEmployee = (arr, id) => {
   for (let i = 0; i < arr.length; i++) {
      if (arr[i].id === id) {
         return [];
      } 
      else if (arr[i].employee && arr[i].employee.length) {
         const t = findEmployee(arr[i].employee, id);

         if (t !== false) {
         if (t.length == 0) 
            t.push({"name": arr[i].name, "surname": arr[i].surname});
            return t;
         }
      }
   }
   return false;
};

console.log(findEmployee(data, "3000"))

But the solution in that thread relies on each child node having the same key 'children' which wouldn't work in my scenario. I'd have to restructure my JSON structure to be:

employee
    employee
       employee
          id

And this doesn't make sense in my scenario. How can I search by id across all leaveRequests and get the employee's name and surname from the immediate parent?

I'd suggest walking through the object recursively, and returning if we find an object with a leaveRequest property.

Once we find this we'll strip off the leaveRequest and return the rest of the object.

 const data = [{ "employees": [ { "employee": [ { "name": "Jon", "surname": "Smith", "leaveRequest": [ { "id": "3000", "approver": "Terry" } ] }], }, { "employee": [ { "name": "Mike", "surname": "Jones", "leaveRequest": [ { "id": "1700", "approver": "Mary" } ] }, ] } ] }]; function findRequest(input, id) { if (input.leaveRequest && input.leaveRequest[0].id == id) { // Return everything bar the leave request... return (({ leaveRequest,...obj}) => obj)(input); } for(let k in input) { if (input[k] && typeof(input[k]) === 'object') { let leaveRequest = findRequest(input[k], id); if (leaveRequest) return leaveRequest; } } } let ids = [1700, 3000]; ids.forEach(id => console.log('id: ', id, '\nrequest:', findRequest(data, id)));
 .as-console-wrapper { max-height: 100%;important }

Here is an answer using object-scan :

 .as-console-wrapper {max-height: 100%;important: top 0}
 <script type="module"> import objectScan from 'https://cdn.jsdelivr.net/npm/object-scan@18.3.0/lib/index.min.js'; const data = [{ employees: [{ employee: [{ name: 'Jon', surname: 'Smith', leaveRequest: [{ id: '3000', approver: 'Terry' }] }] }, { employee: [{ name: 'Mike', surname: 'Jones', leaveRequest: [{ id: '1700', approver: 'Mary' }] }] }] }]; const find = objectScan(['[*].employees[*].employee[*].leaveRequest[*].id'], { abort: true, // abort after first result filterFn: ({ value, context }) => value === context, // find the correct id rtn: ({ parents }) => parents[2] // return the correct node }); console.log(find(data, '3000')); // => { name: 'Jon', surname: 'Smith', leaveRequest: [ { id: '3000', approver: 'Terry' } ] } </script>

Disclaimer : I'm the author of object-scan

Part of the challenge is the data is oddly shaped, with objects containing single-element arrays and arrays containing single-keyed objects. I'd start by reshaping the data to something more rational (using an assumption that the data isn't recursive -- employees don't contain other employees).

// create a flat array of employees [ employeeA, employeeB, ...
const employees = data[0].employees.map(object => object.employee[0]);

With that, the way to go from a leaveRequest id to the containing employee is to build an index. Here, another assumption is that leaveRequest ids are unique.

// create an index { leaveIdX: employeeC, leaveIdY: employeeD, ...
const leaveRequestIndex = employees.reduce((index, employee) => {
  employee.leaveRequest.forEach(request => index[request.id] = employee);
  return index;
}, {});

That provides an O(1) lookup from leaveRequest to employee, like this...

// given someLeaveRequestId, get the containingEmployee
const containingEmployee = leaveRequestIndex[someLeaveRequestId];

Testing on the OP data...

 // create a flat array of employees [ { employee }, { employee }, ... const employees = data()[0].employees.map(object => object.employee[0]); // create an index { leaveId: { employee }, leaveId: { employee }, ... const leaveRequestIndex = employees.reduce((index, employee) => { employee.leaveRequest.forEach(request => index[request.id] = employee); return index; }, {}); // test it: which employee has leave request 3001? const employee = leaveRequestIndex[3001]; const { name, surname } = employee; console.log(name, surname); function data() { return [{ "employees": [ { "employee": [ { "name": "Jon", "surname": "Smith", "leaveRequest": [ { "id": "3000", "approver": "Terry" }, { "id": "3001", "approver": "Larry" } ] }], }, { "employee": [ { "name": "Mike", "surname": "Jones", "leaveRequest": [ { "id": "1700", "approver": "Mary" } ] }, ] } ] }]; }

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