Удалить объект во вложенной структуре по ключу

У меня есть массив с объектами, которые могут иметь дочерние элементы, дочерние элементы имеют ту же структуру, что и родитель, в основном это просто вложение объектов.

Мне интересно, как я могу удалить один из объектов по ключу. Например, я хочу удалить объект с id: 1 (который вложен в дочерний массив другого объекта)

const data = [
  {
    id: 2,
    children: [
      {
        id: 1,
        children: []
      }
    ]
  },
  {
    id: 3,
    children: [],
  }
]

Перемещение детей вверх

Было бы возможно? Если объект с дочерними элементами удаляется, то дочерние элементы перемещаются в корень?

я пробовал

Я попытался переработать следующую функцию, которая возвращает все идентификаторы из моей структуры данных, поэтому она извлекает идентификатор и, если есть дочерние элементы, извлекает идентификаторы внутри этих дочерних элементов. Но как мне удалить объект с этим идентификатором?

export function flattenFindAttribute (data, attribute) {
  return data.map(item => [item[attribute], ...flattenFindAttribute(item.children)]).flat()
}

Ваши значения obj.id всегда уникальны для всех объектов?

d_shiv 22.03.2019 11:13

Да, они взяты из базы данных.

Miguel Stevens 22.03.2019 11:14

Являются ли идентификаторы уникальными. Скажем, может ли идентификатор родителя совпадать с идентификатором ребенка?

DTul 22.03.2019 11:25

Нет, идентификатор всегда будет уникальным на всех уровнях.

Miguel Stevens 22.03.2019 11:39
Поведение ключевого слова "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) для оценки ваших знаний,...
2
4
187
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Вы можете сделать это следующим образом:

const data = [
  {
    id: 2,
    children: [
      {
        id: 1,
        children: []
      }
    ]
  },
  {
    id: 3,
    children: [],
  }
]

let deletedObj = {}

function deleteAtId(arr, deleteId) {
  const rs = []
  arr.forEach(({id, children}) => {
    if (id !== deleteId) {
      if (!children.length) {
        rs.push({id, children})
      } else {
        const tmp = deleteAtId(children, deleteId)
        rs.push({id, children: tmp})
      }
    } else deletedObj = {id, children}
  })
  return rs
}

const rs = [...deleteAtId(data, 1), {...deletedObj}]

console.info(rs)

Спасибо! Забыл упомянуть, не будет ли сложно, если элемент с дочерними элементами будет удален, что дочерние элементы уйдут в корень?

Miguel Stevens 22.03.2019 11:47

@Notflip Я обновил ответ. Просто нужна глобальная переменная для хранения удаленного объекта, а затем добавить в окончательный массив.

bird 22.03.2019 12:00

Что это за $deletedObj, он находится вне функции?

Miguel Stevens 26.03.2019 10:03

Что-то вроде этого? Он повторяется рекурсивно и объединяется, когда находит объект с вашим идентификатором.

function removeObject(data, id){
  data.forEach(point =>{
    if (point.children.length > 0){
      removeObject(point.children, id);
    }
    const index = data.findIndex(x => x.id == id);
    if (index > -1){
      data.splice(index ,1);
    }
  });
  return data;
}

const data = [
  {
    id: 2,
    children: [
      {
        id: 1,
        children: []
      }
    ]
  },
  {
    id: 3,
    children: [],
  }
]

removeObject(data, 1);

console.info(data)

Спасибо! Забыл упомянуть, не будет ли сложно, если элемент с дочерними элементами будет удален, что дочерние элементы уйдут в корень?

Miguel Stevens 22.03.2019 11:47
Ответ принят как подходящий

Вам просто нужно использовать рекурсивный вызов function и использовать Array#splice() метод для удаления искомого object.

Вот как должен быть ваш код:

function removeId(data, id) {
  data.forEach((o, i) => {
    if (o.id && o.id === id) {
      data.splice(i, 1);
      return true;
    } else if (o.children) {
      removeId(o.children, id);
    }
  });
}

Демо:

const data = [{
    id: 2,
    children: [{
      id: 1,
      children: []
    }]
  },
  {
    id: 3,
    children: [],
  }
];

function removeId(data, id) {
  data.forEach((o, i) => {
    if (o.id && o.id === id) {
      data.splice(i, 1);
      return true;
    } else if (o.children) {
      removeId(o.children, id);
    }
  });
}

removeId(data, 1);
console.info(data);

Редактировать:

Если вы хотите поместить весь удаленный элемент children в его родительский массив children, вам просто нужно передать третий параметр вашей функции, чтобы отслеживать родителя object:

function removeId(data, id, parent) {
  data.forEach((o, i) => {
    if (o.id && o.id === id) {
      if (parent) {
        o.children.forEach(c => parent.children.push(c));
      }
      data.splice(i, 1);
      return true;
    } else if (o.children) {
      removeId(o.children, id, o);
    }
  });
}

Демо:

var data = [{
    id: 2,
    children: [{
      id: 1,
      children: [1, 2]
    }]
  },
  {
    id: 3,
    children: [],
  }
];

function removeId(data, id, parent) {
  data.forEach((o, i) => {
    if (o.id && o.id === id) {
      if (parent) {
        o.children.forEach(c=> parent.children.push(c));
      }
      data.splice(i, 1);
      return true;
    } else if (o.children) {
      removeId(o.children, id, o);
    }
  });
}

removeId(data, 1);
console.info(data);

Это работает, я забыл упомянуть, будет ли сложно, если элемент с детьми будет удален, чтобы дети ушли в корень?

Miguel Stevens 22.03.2019 11:46

@Notflip гул не сложный, но с учетом рекурсии это будет не так просто. потому что при вызове функции мы теряем контекст родителя.

cнŝdk 22.03.2019 11:55

Спасибо за ваш ответ, сейчас я пытаюсь реализовать его в вашей функции, но не думаю, что это будет легко

Miguel Stevens 22.03.2019 11:57

@Notflip На самом деле это может быть легко, нам просто нужно передать родителя object в function, проверьте мое редактирование для решения.

cнŝdk 22.03.2019 11:59

Возможно ли это и при использовании array.map? Так как я использую Vue, то пытаюсь добиться результата вашим методом, но таким, который возвращает, например: const newData = removeId(data, 1). Поскольку теперь он обновляет переданный объект данных, а не создает новый с результатом.

Miguel Stevens 26.03.2019 10:04

Ну, вы можете сделать это с обоими, вам просто нужно идеально обработать возвращаемый объект.

cнŝdk 26.03.2019 10:23

Спасибо за ваш быстрый ответ! Я пытался использовать array.map(), но он нарушает рекурсивность, возвращает undefined

Miguel Stevens 26.03.2019 10:23

Вы возвращаете объект на каждой итерации?

cнŝdk 26.03.2019 12:25

Исследуйте объект рекурсивно и удалите в соответствии с указанным предикатом:

const data =[...Array(4)].map(()=> [
  {
    id: 2,
    children: [
      {
        id: 1,
        children: []
      }
    ]
  },
  {
    id: 3,
    children: [],
  }
]);

function deleteObj(parent, predicate) {
  if (predicate(parent)) {
    return true;
  }
  if (typeof parent === 'object') {
    if (Array.isArray(parent)) {
      for (let i = 0; i < parent.length; i++) {
        if (deleteObj(parent[i], predicate)) {
          parent.splice(i, 1);
          i--;
        }
      } 
    } else {
      Object.keys(parent).forEach(key => deleteObj(parent[key], predicate) && delete parent[key]);
    }
  }
  return false;
}

console.info('from array:', data[0]);

const test1 = data[1];
const test2 = data[2];
const test3 = data[3];

console.info('delete node with id === 1');
deleteObj(test1, node => node.id === 1);
console.info(test1);

console.info('delete node with id === 3');
deleteObj(test2, node => node.id === 3);
console.info(test2);

console.info('delete node with non empty children');
deleteObj(test3, node => node.children && node.children.length > 0);
console.info(test3);

Эта рекурсивная функция работает для ваших нужд:

function rotateChildren(array, key) {
   if (!array || !array.length) {
      return array;
   }

   return array.filter(child => {
      if (child) {
         child.children = rotateChildren(child.children, key);
         return child.id !== key;
      }

      return !!child;
   });
}

const data = [
   {
      id: 2,
      children: [
         {
            id: 1,
            children: []
         }
      ]
   },
   {
      id: 3,
      children: [],
   }
];

console.info(rotateChildren(data, 1));
console.info(rotateChildren(data, 2));
console.info(rotateChildren(data, 3));

Было бы возможно? Если объект с дочерними элементами удаляется, то дочерние элементы перемещаются в корень?

Miguel Stevens 22.03.2019 11:48

Вы можете добиться этого, используя Array.reduce с рекурсией для детей.

Пример:

const data = [
  { id: 1, children: [{ id: 2, children: [] }] },
  {
    id: 2,
    children: [
      {
        id: 1,
        children: [],
      },
    ],
  },
  {
    id: 3,
    children: [],
  },
  {
    id: 4,
    children: [
      {
        id: 2,
        children: [
          {
            id: 2,
            children: [
              {
                id: 1,
                children: [],
              },
              {
                id: 2,
                children: [],
              },
            ],
          },
        ],
      },
    ],
  },
];

function removeBy(data, predicate) {
  return data.reduce((result, item) => {
    if (!predicate(item.id)) {
      const newItem = { ...item };
      if (item.children.length > 0) {
        newItem.children = removeBy(item.children, predicate);
      }
      result.push(newItem);
    }
    return result;
  }, []);
}

const predicate = value => value === 1;
const result = removeBy(data, predicate);

console.info(result);

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