Проблема фильтрации вложенных массивов JavaScript

Учитывая объект JavaScript, который представляет такой JSON -

[
    {
        "Id": "8868dfdd-9b4e-4bad-a4ce-ecae6a3cc828",
        "Name": "Company 1",
        "Locations": [
            {
                "Id": "bd017b9c-b62e-43aa-9f00-c164a855eed1",
                "Name": "Location 1",
                "Departments": [
                    {
                        "Id": "c9e4afe3-bbdb-474f-9062-2935025bfa2e",
                        "Name": "Department 1",
                        "Employees": [
                            {
                                "Id": "92c3a085-5712-422d-8b0f-922b57889c4f",
                                "Name": "Employee 1",
                                "Title": "FrontEnd Engineer",
                                "Location": "New York",
                                "Photo": ""
                            }
                        ]
                    }
                ]
            }
        ]
    }
]

Я хочу отфильтровать эту структуру данных по имени сотрудника, учитывая, что может быть несколько компаний, местоположений, отделов. Вот моя попытка, но она явно не работает из-за моего понимания того, как работают Array.filter или Array.reduce.

filterContacts = search => {
    if (search.trim() === "") {
        this.setState({ filteredContacts: null, search: search });
    } else {
        let filteredArray = this.state.contacts.reduce((f, c) => {
            let clone = [];
            for (let i = 0; i < c.Locations.length; i++) {
                const l = c.Locations[i];
                for (let j = 0; j < l.Departments.length; j++) {
                    const d = l.Departments[j];
                    for (let k = 0; k < d.Employees.length; k++) {
                        const e = d.Employees[k];
                        if (e.Name.search(new RegExp(search, "i") > -1)) {
                            clone.push(l);
                        }
                    }
                }
            }
            return clone;
        }, []);
        this.setState({ filteredContacts: filteredArray, search: search });
    }
};

Любая помощь приветствуется. Спасибо.

Какой результат вы ожидаете?

CertainPerformance 25.12.2018 18:25

Если вы измените let clone = []; на let clone = f;, он должен работать. Но можно упростить с помощью .some

Gabriele Petrioli 25.12.2018 18:30
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
2
123
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Когда вы используете:

 let clone = [];

в верхней части обратного вызова reduce() вы выбрасываете аккумулятор - массив, который продолжает передаваться в цикле, который передается как f в вашем коде. Вы должны использовать один и тот же аккумулятор reduce каждый раз и вставлять в него. В конце у вас будет массив всех значений:

let arr = [{"Id": "8868dfdd-9b4e-4bad-a4ce-ecae6a3cc828","Name": "Company 1","Locations": [{"Id": "bd017b9c-b62e-43aa-9f00-c164a855eed1","Name": "Location 1","Departments": [{"Id": "c9e4afe3-bbdb-474f-9062-2935025bfa2e","Name": "Department 1","Employees": [{"Id": "92c3a085-5712-422d-8b0f-922b57889c4f","Name": "Employee 1","Title": "FrontEnd Engineer","Location": "New York","Photo": ""}]}]}]}]

let emp = arr.reduce((f, obj) => {
  obj.Locations.forEach(location => 
    location.Departments.forEach(department => 
      f.push(...department.Employees.filter(emp => emp.Name == "Employee 1"))
    )
  )
  return f
}, []) // <-- this array will get passed to every loop as `f`

console.info(emp)

ИЗМЕНИТЬ на основе комментария

Если вы хотите сохранить структуру, вы можете фильтровать массивы на основе длины отфильтрованного массива под ними. Вот пример с некоторыми дополнительными данными, см. Работу фильтрации. Первый полностью фильтруется, в третьем - два сотрудника с одинаковыми именами. По сути, он сохранит любой элемент в местоположении, в котором есть отдел, в котором есть соответствующий сотрудник:

let arr = [
  {"Id": "someother","Name": "Company 2","Locations": [{"Id": "loc2Id","Name": "Location 2","Departments": [{"Id": "d2","Name": "Department 2","Employees": [{"Id": "emp","Name": "Employee 2","Title": "FrontEnd Engineer","Location": "New York","Photo": ""}]}]}]}, 
  {"Id": "8868dfdd-9b4e-4bad-a4ce-ecae6a3cc828","Name": "Company 1","Locations": [{"Id": "bd017b9c-b62e-43aa-9f00-c164a855eed1","Name": "Location 1","Departments": [{"Id": "c9e4afe3-bbdb-474f-9062-2935025bfa2e","Name": "Department 1","Employees": [{"Id": "92c3a085-5712-422d-8b0f-922b57889c4f","Name": "Employee 1","Title": "FrontEnd Engineer","Location": "New York","Photo": ""}]}]}]},
  {"Id": "someother","Name": "Company 2","Locations": [{"Id": "loc2Id","Name": "Location 2","Departments": [{"Id": "d2","Name": "Department 2","Employees": [{"Id": "emp","Name": "Employee 1","Title": "FrontEnd Engineer","Location": "New York","Photo": ""}, {"Id": "emp","Name": "Employee 1","Title": "FrontEnd Engineer 2","Location": "New York","Photo": ""}]}]}]}, 
]


let f = []
let emp = arr.filter(arr => 
  arr.Locations.filter(location => 
    location.Departments.filter(department => {
      let emp = department.Employees.filter(emp => emp.Name == "Employee 1")
      return emp.length ? emp: false 
    }
    ).length
  ).length
) // <-- this array will get passed to every loop as `f`

console.info(emp)

Является ли использование forEach вашим личным предпочтением? Я поддержал, особенно потому, что видел .push (... items) - никогда не думал, что такой синтаксис возможен, полезно знать. Я бы использовал .map, но это мое личное предпочтение.

Valery Baranov 25.12.2018 19:04

Спасибо @ValeryBaranov. Да, обычный цикл for тоже подойдет (а иногда и быстрее). Думаю, forEach легче на глазах. Это менее многословно и легче понять, что именно происходит (если вы используете правильные имена переменных). map() здесь не совсем подходит, потому что вы не возвращаете массивы из этих циклов, поэтому нет причин страховать накладные расходы на их создание.

Mark 25.12.2018 19:07

@MarkMeyer Я хочу сохранить иерархию Company -> Location -> Department, чтобы при повторном рендеринге элементы фильтровались прямо из узла верхнего уровня.

Soham Dasgupta 26.12.2018 07:33

Вот еще одна короткая версия с использованием карты:

var rx=new RegExp(search,'i'),emp=[];
obj.map(c=>
 c.Locations.map(l=>
 l.Departments.map(d=>
 d.Employees.map(e=>
  {if (e.Name.match(rx)) emp.push(e)}
))));

search содержит шаблон поиска без учета регистра. Результатом является emp, массив объектов сотрудников.

Как упоминалось выше, map на самом деле не нужна и может быть заменена на forEach, но, на мой взгляд, ее проще написать и на самом деле она не вызывает значительно больших накладных расходов.

Редактировать, на этот раз с использованием reduce():

Сейчас Рождество, и у меня слишком много времени, и я продолжаю играть. Следующее решение отфильтрует искомых сотрудников, не показывая их несовпадающих коллег и оставляя исходный массив нетронутым:

const rd=(prop,fun)=>
             (a,el)=>{
var arr=el[prop].reduce(fun,[]);
if (arr.length){
  var r=Object.assign({},el);
// alternatively: use spread operator
// var r = {...el};
  r[prop]=arr;a.push(r);}
return a;}

var rx=new RegExp('employee 1','i');

 var f=ma.reduce(
  rd('Locations',
  rd('Departments',
  rd('Employees',(a,e)=>{
     if (e.Name.match(rx))
      a.push(e);
     return a;}
,[]),[]),[]),[]);

f будет содержать массив, содержащий только те местоположения, отделы и сотрудников, где сотрудники будут соответствовать регулярному выражению rx.

rd() - это функция генератора, возвращающая фактические функции фильтрации, которые используются на трех разных уровнях reduce.

Статическая функция Object.assign() - это простой способ создания неглубокой копии объекта (аналогично методу slice() для массивов).

Другие вопросы по теме