Учитывая объект 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 });
}
};
Любая помощь приветствуется. Спасибо.
Если вы измените let clone = []; на let clone = f;, он должен работать. Но можно упростить с помощью .some



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Когда вы используете:
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, но это мое личное предпочтение.
Спасибо @ValeryBaranov. Да, обычный цикл for тоже подойдет (а иногда и быстрее). Думаю, forEach легче на глазах. Это менее многословно и легче понять, что именно происходит (если вы используете правильные имена переменных). map() здесь не совсем подходит, потому что вы не возвращаете массивы из этих циклов, поэтому нет причин страховать накладные расходы на их создание.
@MarkMeyer Я хочу сохранить иерархию Company -> Location -> Department, чтобы при повторном рендеринге элементы фильтровались прямо из узла верхнего уровня.
Вот еще одна короткая версия с использованием карты:
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() для массивов).
Какой результат вы ожидаете?