How to get a subset of a javascript object's properties

后端 未结 27 2071
刺人心
刺人心 2020-11-21 22:46

Say I have an object:

elmo = { 
  color: \'red\',
  annoying: true,
  height: \'unknown\',
  meta: { one: \'1\', two: \'2\'}
};

I want to m

27条回答
  •  萌比男神i
    2020-11-21 23:26

    ES6 destructuring

    Destructuring syntax allows to destructure and recombine an object, with either function parameters or variables.

    The limitation is that a list of keys is predefined, they cannot be listed as strings, as the question mentions. Destructuring becomes more complicated if a key is non-alphanumeric, e.g. foo_bar.

    The downside is that this requires to duplicate a list of keys, this results in verbose code in case a list is long. Since destructuring duplicates object literal syntax in this case, a list can be copied and pasted as is.

    The upside is that it's performant solution that is natural to ES6.

    IIFE

    let subset = (({ foo, bar }) => ({ foo, bar }))(obj); // dupe ({ foo, bar })
    

    Temporary variables

    let { foo, bar } = obj;
    let subset = { foo, bar }; // dupe { foo, bar }
    

    A list of strings

    Arbitrary list of picked keys consists of strings, as the question requires. This allows to not predefine them and use variables that contain key names, like pick(obj, 'foo', someKey, ...moreKeys).

    A one-liner becomes shorter with each JS edition.

    ES5

    var subset = Object.keys(obj)
    .filter(function (key) { 
      return ['foo', 'bar'].indexOf(key) >= 0;
    })
    .reduce(function (obj2, key) {
      obj2[key] = obj[key];
      return obj2;
    }, {});
    

    ES6

    let subset = Object.keys(obj)
    .filter(key => ['foo', 'bar'].indexOf(key) >= 0)
    .reduce((obj2, key) => Object.assign(obj2, { [key]: obj[key] }), {});
    

    Or with comma operator:

    let subset = Object.keys(obj)
    .filter(key => ['foo', 'bar'].indexOf(key) >= 0)
    .reduce((obj2, key) => (obj2[key] = obj[key], obj2), {});
    

    ES2019

    ECMAScript 2017 has Object.entries and Array.prototype.includes, ECMAScript 2019 has Object.fromEntries, they can be polyfilled when needed and make the task easier:

    let subset = Object.fromEntries(
      Object.entries(obj)
      .filter(([key]) => ['foo', 'bar'].includes(key))
    )
    

    A one-liner can be rewritten as helper function similar to Lodash pick or omit where the list of keys is passed through arguments:

    let pick = (obj, ...keys) => Object.fromEntries(
      Object.entries(obj)
      .filter(([key]) => keys.includes(key))
    );
    
    let subset = pick({ foo: 1, qux: 2 }, 'foo', 'bar'); // { foo: 1 }
    

    A note about missing keys

    The major difference between destructuring and conventional Lodash-like pick function is that destructuring includes non-existent picked keys with undefined value in a subset:

    (({ foo, bar }) => ({ foo, bar }))({ foo: 1 }) // { foo: 1, bar: undefined }
    

    This behaviour may or not be desirable. It cannot be changed for destructuring syntax.

    While pick can be changed to include missing keys by iterating a list of picked keys instead:

    let inclusivePick = (obj, ...keys) => Object.fromEntries(
      keys.map(key => [key, obj[key]])
    );
    
    let subset = inclusivePick({ foo: 1, qux: 2 }, 'foo', 'bar'); // { foo: 1, bar: undefined }
    

提交回复
热议问题