Фильтровать свойства объекта по ключу в ES6


267

Допустим, у меня есть объект:

{
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
}

Я хочу создать еще один объект, отфильтровав объект выше, чтобы у меня было что-то вроде.

 {
    item1: { key: 'sdfd', value:'sdfd' },
    item3: { key: 'sdfd', value:'sdfd' }
 }

Я ищу чистый способ сделать это с помощью Es6, поэтому мне доступны операторы спреда.


В ES6 нет операторов распространения объектов, и они вам здесь все равно не нужны
Bergi


@DanDascalescu Но этот ответ дает ES6 способ выполнить то, что спрашивает ОП, не так ли?
Джонатан Х

Что, если я хочу фильтровать по ключу / значению?
19

Ответы:


504

Если у вас есть список допустимых значений, вы можете легко сохранить их в объекте, используя:

const raw = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const allowed = ['item1', 'item3'];

const filtered = Object.keys(raw)
  .filter(key => allowed.includes(key))
  .reduce((obj, key) => {
    obj[key] = raw[key];
    return obj;
  }, {});

console.log(filtered);

Это использует:

  1. Object.keysперечислить все свойства в raw(исходные данные), затем
  2. Array.prototype.filter выбрать ключи, которые присутствуют в списке разрешенных, используя
    1. Array.prototype.includes чтобы убедиться, что они присутствуют
  3. Array.prototype.reduce построить новый объект только с разрешенными свойствами.

Это сделает поверхностную копию с разрешенными свойствами (но не скопирует сами свойства).

Вы также можете использовать оператор распространения объекта, чтобы создать серию объектов, не изменяя их (спасибо rjerue за упоминание этого ):

const raw = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const allowed = ['item1', 'item3'];

const filtered = Object.keys(raw)
  .filter(key => allowed.includes(key))
  .reduce((obj, key) => {
    return {
      ...obj,
      [key]: raw[key]
    };
  }, {});

console.log(filtered);

В целях упрощения, если вы хотите удалить ненужные поля из исходных данных (что я бы не рекомендовал делать, так как это связано с некоторыми уродливыми мутациями), вы можете инвертировать includesпроверку следующим образом:

const raw = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const allowed = ['item1', 'item3'];

Object.keys(raw)
  .filter(key => !allowed.includes(key))
  .forEach(key => delete raw[key]);

console.log(raw);

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


1
Спасибо, что отлично сработало. Я также нашел подход с использованием синтаксиса деконструкции. IE: const {item1, item3} = необработанный const newObject = {item1, item3}
29

2
Деконструкция будет работать (просто отлично), но это чисто время компиляции. Вы не можете сделать его динамическим списком свойств или предоставить какие-либо сложные правила (например, к циклу могут быть прикреплены обратные вызовы проверки).
ssube

3
Я не могу проголосовать достаточно! Хорошо сделано для использования фильтра и уменьшения и не построения другого объекта из цикла for. И замечательно, что вы явно разделили неизменяемую и изменчивую версии. +1
Sukima

3
Быстрое предупреждение: Array.prototype.includesне является частью ES6. Он представлен в ECMAScript 2016 (ES7).
Vineet

3
Если вы хотите сделать редукцию неизменным образом, вы также можете заменить содержимое функции на return {... obj, [key]: raw [key]}
rjerue

93

Если вы в порядке с использованием синтаксиса ES6, я считаю, что самый чистый способ сделать это, как отмечено здесь и здесь :

const data = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const { item2, ...newData } = data;

Теперь newDataсодержит:

{
  item1: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

Или, если ключ хранится в виде строки:

const key = 'item2';
const { [key]: _, ...newData } = data;

В последнем случае [key]преобразуется в, item2но, поскольку вы используете constназначение, вам необходимо указать имя для назначения. _представляет собой выброшенное значение.

В более общем смысле:

const { item2, ...newData } = data; // Assign item2 to item2
const { item2: someVarName, ...newData } = data; // Assign item2 to someVarName
const { item2: _, ...newData } = data; // Assign item2 to _
const { ['item2']: _, ...newData } = data; // Convert string to key first, ...

Это не только сокращает вашу работу до одной строки, но также не требует, чтобы вы знали, каковы другие ключи (те, которые вы хотите сохранить).


5
" _представляет собой выбрасываемую стоимость", откуда это приходит? Первый раз вижу
yhabib

5
Я считаю, что это принятая конвенция сообщества JS. An _- это просто допустимое имя переменной, которое можно использовать в JS, но, поскольку оно в значительной степени безымянно, его не следует использовать таким образом, если вы хотите передать намерение. Итак, он был принят как способ обозначить переменную, которая вас не волнует. Вот дальнейшее обсуждение этого вопроса: stackoverflow.com/questions/11406823/…
Райан Х.

2
Это намного аккуратнее, чем принятый ответ, позволяет избежать затрат на создание нового массива с помощью Object.keys () и затрат на итерацию массива с помощью filterи reduce.
ericsoco

3
@yhabib _не имеет значения, это просто имя переменной, вы можете переименовать ее как угодно
Vic

1
@Gerrat Я не думаю, что обобщенное решение будет тривиальным однострочным. Для этого я бы использовал omitфункцию lodash : lodash.com/docs/4.17.10#omit или одно из приведенных здесь решений.
Райан Х.

42

Самый чистый способ найти это с помощью Lodash # pick

const _ = require('lodash');

const allowed = ['item1', 'item3'];

const obj = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
}

const filteredObj = _.pick(obj, allowed)

3
Важно отметить, что для этого необходимо загружать дополнительную зависимость проекта во время выполнения.
С. Эстевес

1
_.omit (obj, ["item2"]) - lodash.omit является противоположностью lodash.pick
Александр Соловьев

29

Ничего из того, что не было сказано ранее, кроме как объединить некоторые ответы на общий ответ ES6:

const raw = {
  item1: { key: 'sdfd', value: 'sdfd' },
  item2: { key: 'sdfd', value: 'sdfd' },
  item3: { key: 'sdfd', value: 'sdfd' }
};

const filteredKeys = ['item1', 'item3'];

const filtered = filteredKeys
  .reduce((obj, key) => ({ ...obj, [key]: raw[key] }), {});

console.log(filtered);


1
это гораздо более простое и эффективное решение, чем другие;)
Помех

26

Просто еще одно решение в одной линии Modern JS без внешних библиотек .

Я играл с функцией « Разрушение »:

const raw = {
    item1: { key: 'sdfd', value: 'sdfd' },
    item2: { key: 'sdfd', value: 'sdfd' },
    item3: { key: 'sdfd', value: 'sdfd' }
  };
var myNewRaw = (({ item1, item3}) => ({ item1, item3 }))(raw);
console.log(myNewRaw);


2
var myNewRaw = (({ item1, item3}) => ({ item1, item3 }))(raw);Синтаксическая проблема
Awol

1
Здесь сгущено несколько вещей: во-первых, это объявление функции. это функция стрелки (она берет объект и возвращает объект). Это то же самое, что и функция (obj) {return obj;}. Второе - ES6 позволит разрушить будущее. В моем объявлении функции я деструктурирую свой объект {item1, item3}. И последнее, что я сам называю своей функцией. Вы можете использовать функцию самостоятельного вызова, например, для управления своей областью. Но здесь это было просто для сжатия кода. Надеюсь понятно. Если я что-то упустил, не стесняйтесь добавлять больше.
Новый

5
это предпочтительный современный подход imho
mattdlockyer

20

Теперь вы можете сделать его короче и проще, используя метод Object.fromEntries (проверьте поддержку браузера):

const raw = { item1: { prop:'1' }, item2: { prop:'2' }, item3: { prop:'3' } };

const allowed = ['item1', 'item3'];

const filtered = Object.fromEntries(
   Object.entries(raw).filter(
      ([key, val])=>allowed.includes(key)
   )
);

Узнайте больше о: Object.fromEntries


1
Это сейчас лучший способ, я думаю. Гораздо более интуитивно, чем уменьшить.
Деймон

10

Вы можете добавить универсальный ofilter(реализованный с универсальным oreduce), чтобы вы могли легко фильтровать объекты так же, как вы можете массивов -

const oreduce = (f, acc, o) =>
  Object
    .entries (o)
    .reduce
      ( (acc, [ k, v ]) => f (acc, v, k, o)
      , acc
      )

const ofilter = (f, o) =>
  oreduce
    ( (acc, v, k, o)=>
        f (v, k, o)
          ? Object.assign (acc, {[k]: v})
          : acc
    , {}
    , o
    )

Мы можем видеть это работает здесь -

const data =
  { item1: { key: 'a', value: 1 }
  , item2: { key: 'b', value: 2 }
  , item3: { key: 'c', value: 3 }
  }

console.log
  ( ofilter
      ( (v, k) => k !== 'item2'
      , data
      )
      // [ { item1: { key: 'a', value: 1 } }
      // , { item3: { key: 'c', value: 3 } }
      // ]

  , ofilter
      ( x => x.value === 3
      , data
      )
      // [ { item3: { key: 'c', value: 3 } } ]
  )

Проверьте результаты в вашем собственном браузере ниже -

Эти две функции могут быть реализованы многими способами. Я решил прикрепить Array.prototype.reduceвнутри, oreduceно вы могли бы так же легко написать все это с нуля


Мне нравится ваше решение, но я не знаю, насколько оно более четкое / более эффективное по сравнению с этим .
Джонатан Х

3
Вот тест, показывающий, что ваше решение самое быстрое.
Джонатан Х

В oreduceпервый accбудет перекрыт .reduce(acc, k), и в затенена - вызывается с переменной называется так же - что есть что? ofilterooreduceo
Джаррод Мозен

в ofilterпеременной oзатенена , но оно всегда указывает на тот же входной вар; вот почему он скрыт здесь, потому что он тот же - аккумулятор ( acc) также скрыт, потому что он более четко показывает, как данные проходят через лямбду; accне всегда
спасибо

Хотя код работает хорошо, а метод очень хорош, я удивляюсь, что такое помощь в написании кода кому-то, кто явно нуждается в помощи с javascript. Я все для краткости, но это почти так же компактно, как свернутый код.
Paul G Mihai

7

Вот как я это сделал недавно:

const dummyObj = Object.assign({}, obj);
delete dummyObj[key];
const target = Object.assign({}, {...dummyObj});

Гектометр Кажется, вы смешиваете старый и новый синтаксис. Object.assign == ...Вы могли бы написать const dummyObj = { ...obj }и const target = { ...dummyObj }. Кроме того, последнее совсем не нужно, так как впоследствии вы можете напрямую работать с dummyObj.
Энди

7

хорошо, как насчет этой строки

    const raw = {
      item1: { key: 'sdfd', value: 'sdfd' },
      item2: { key: 'sdfd', value: 'sdfd' },
      item3: { key: 'sdfd', value: 'sdfd' }
    };

    const filteredKeys = ['item1', 'item3'];

    const filtered = Object.assign({}, ...filteredKeys.map(key=> ({[key]:raw[key]})));

2
Самое удивительное решение из всех ответов. +1
Герго Хорват

Что делать, если вы знаете только ключи, которые хотите удалить ... не те ключи, которые вы хотите сохранить?
Джейсон

6

Ответы здесь определенно подходят, но они немного медленные, потому что они требуют циклического прохождения белого списка для каждого свойства объекта. Приведенное ниже решение гораздо быстрее для больших наборов данных, поскольку оно проходит через белый список только один раз:

const data = {
  allowed1: 'blah',
  allowed2: 'blah blah',
  notAllowed: 'woah',
  superSensitiveInfo: 'whooooah',
  allowed3: 'bleh'
};

const whitelist = ['allowed1', 'allowed2', 'allowed3'];

function sanitize(data, whitelist) {
    return whitelist.reduce(
      (result, key) =>
        data[key] !== undefined
          ? Object.assign(result, { [key]: data[key] })
          : result,
      {}
    );
  }

  sanitize(data, whitelist)

5

Спекуляция на ответе ssube .

Вот многоразовая версия.

Object.filterByKey = function (obj, predicate) {
  return Object.keys(obj)
    .filter(key => predicate(key))
    .reduce((out, key) => {
      out[key] = obj[key];
      return out;
    }, {});
}

Чтобы назвать это использовать

const raw = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const allowed = ['item1', 'item3'];

var filtered = Object.filterByKey(raw, key => 
  return allowed.includes(key));
});

console.log(filtered);

Прекрасная особенность функций стрелок ES6 заключается в том, что вам не нужно передавать в allowedкачестве параметра.


4

Вы можете сделать что-то вроде этого:

const base = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const filtered = (
    source => { 
        with(source){ 
            return {item1, item3} 
        } 
    }
)(base);

// one line
const filtered = (source => { with(source){ return {item1, item3} } })(base);

Это работает, но не очень понятно, плюс withутверждение не рекомендуется ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with ).


4

Более простое решение без использования filterможет быть достигнуто с помощью Object.entries()вместоObject.keys()

const raw = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const allowed = ['item1', 'item3'];

const filtered = Object.entries(raw).reduce((acc,elm)=>{
  const [k,v] = elm
  if (allowed.includes(k)) {
    acc[k] = v 
  }
  return acc
},{})

3

Есть много способов сделать это. Общепринятый ответ использует ключи-Filter-Reduce подход, который не является самым производительным.

Вместо этого использование for...inцикла для циклического перебора ключей объекта или циклического перебора разрешенных ключей, а затем составление нового объекта повышает производительность на ~ 50% a .

const obj = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const keys = ['item1', 'item3'];

function keysReduce (obj, keys) {
  return keys.reduce((acc, key) => {
    if(obj[key] !== undefined) {
      acc[key] = obj[key];
    }
    return acc;
  }, {});
};

function forInCompose (obj, keys) {
  const returnObj = {};
  for (const key in obj) {
    if(keys.includes(key)) {
      returnObj[key] = obj[key]
    }
  };
  return returnObj;
};

keysReduce(obj, keys);   // Faster if the list of allowed keys are short
forInCompose(obj, keys); // Faster if the number of object properties are low

а. Посмотрите jsPerf для тестов простого варианта использования. Результаты будут отличаться в зависимости от браузера.


3

Вы можете удалить специальный ключ на вашем объекте

items={
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
}

// Example 1
var key = "item2";
delete items[key]; 

// Example 2
delete items["item2"];

// Example 3
delete items.item2;

3
const filteredObject = Object.fromEntries(Object.entries(originalObject).filter(([key, value]) => key !== uuid))

2
Есть другие ответы, которые предоставляют вопрос ОП, и они были опубликованы некоторое время назад. При публикации ответа обязательно добавьте новое решение или существенно лучшее объяснение, особенно при ответе на старые вопросы.
help-info.de

2

Простой способ! Сделать это.

const myData = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};
const{item1,item3}=myData
const result =({item1,item3})


Вы на самом деле ничего не фильтруете здесь, просто уничтожаете данные. Но что происходит при изменении данных?
Идрис Допико Пенья

2

Еще одно решение с использованием «нового» Array.reduceметода:

const raw = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const allowed = ['item1', 'item3'];

const filtered = allowed.reduce((obj, key) => { 
  obj[key] = raw[key]; 
  return obj 
}, {})

console.log(filtered);

Демонстрация в этой скрипке ...


Но мне нравится решение в этом ответе, которое использует и :Object.fromEntries Array.filterArray.includes

const object = Object.fromEntries( Object.entries(raw).filter(([key, value]) => allowed.includes(key)) );

Демонстрация в этой скрипке ...


Уилт, ваш второй подход является точной копией этого ответа, предоставленного в прошлом году: stackoverflow.com/a/56081419/5522000
Арт Шмидт,

@ArtSchmidt Спасибо за ваш комментарий: пропустил этот в длинном списке. Вместо этого я буду ссылаться на этот ответ.
Уилт

1

Хорошо, как насчет этого:

const myData = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

function filteredObject(obj, filter) {
  if(!Array.isArray(filter)) {
   filter = [filter.toString()];
  }
  const newObj = {};
  for(i in obj) {
    if(!filter.includes(i)) {
      newObj[i] = obj[i];
    }
  }
  return newObj;
}

и назовите это так:

filteredObject(myData, ['item2']); //{item1: { key: 'sdfd', value:'sdfd' }, item3: { key: 'sdfd', value:'sdfd' }}

1

Эта функция будет фильтровать объект по списку ключей, это более эффективно, чем предыдущий ответ, так как ей не нужно использовать Array.filter перед вызовом Reduce. поэтому его O (n) в отличие от O (n + фильтруется)

function filterObjectByKeys (object, keys) {
  return Object.keys(object).reduce((accum, key) => {
    if (keys.includes(key)) {
      return { ...accum, [key]: object[key] }
    } else {
      return accum
    }
  }, {})
}

1

Во время цикла ничего не возвращайте при обнаружении определенных свойств / ключей и продолжайте с остальными:

const loop = product =>
Object.keys(product).map(key => {
    if (key === "_id" || key === "__v") {
        return; 
    }
    return (
        <ul className="list-group">
            <li>
                {product[key]}
                <span>
                    {key}
                </span>
            </li>
        </ul>
    );
});

1

Я удивлен, как никто еще не предложил это. Это очень чисто и очень четко о том, какие ключи вы хотите сохранить.

const unfilteredObj = {a: ..., b:..., c:..., x:..., y:...}

const filterObject = ({a,b,c}) => ({a,b,c})
const filteredObject = filterObject(unfilteredObject)

Или если вы хотите грязный лайнер:

const unfilteredObj = {a: ..., b:..., c:..., x:..., y:...}

const filteredObject = (({a,b,c})=>({a,b,c}))(unfilteredObject);

Этот метод добавит ключ, отсутствующий в исходном массиве. Может быть проблема или нет, это должно быть по крайней мере указано.
Понкиетто

0

Другой подход будет использовать Array.prototype.forEach()как

const raw = {
  item1: {
    key: 'sdfd',
    value: 'sdfd'
  },
  item2: {
    key: 'sdfd',
    value: 'sdfd'
  },
  item3: {
    key: 'sdfd',
    value: 'sdfd'
  }
};

const allowed = ['item1', 'item3', 'lll'];

var finalObj = {};
allowed.forEach(allowedVal => {
  if (raw[allowedVal])
    finalObj[allowedVal] = raw[allowedVal]
})
console.log(finalObj)

Он включает в себя значения только тех ключей, которые доступны в необработанных данных и, таким образом, предотвращают добавление ненужных данных.


0

Это было бы мое решение:

const filterObject = (obj, condition) => {
    const filteredObj = {};
    Object.keys(obj).map(key => {
      if (condition(key)) {
        dataFiltered[key] = obj[key];
      }
    });
  return filteredObj;
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.