4

Using Object rest destructuring is straightforward to blacklist properties of an object, like in the following example:

const original = {
  a: 1,
  b: 2,
  c: 3,
  evil: "evil",
  ugly: "ugly",
};

const { evil, ugly, ...sanitized } = original;

console.log(sanitized);   // prints { a: 1, b: 2, c: 3 }

I wonder if there exists a similar terse way to do the same, but using a white list of properties (in the example: { a, b, c }). Very often, I have to convert a subset of the available properties as JSON, and such a functionality would make the code much more readable and safer.

I found a similar question, but it is not exactly the same problem: Is there a more terse way to map properties of one object to another in ES6/ES2015?

Edit: It is a pity that the next code doesn't work, as it is returning the original object instead of the filtered one.

const sanitized = {a, b, c} = original;     
// sanitized === original
Community
  • 1
  • 1
Manolo Santos
  • 1,820
  • 1
  • 14
  • 23
  • 3
    Do you mean having a rest object with only specific properties? No, not in one step. You could do `const { a, b } = original; const new = { a, b };` but you won't get more concise than this. – nils Aug 25 '16 at 14:20
  • @nils not necessarily in rest. I want to know if there exists, or is proposed, an idiomatic way of copying a subset of white-listed properties. I know the solution you proposed, but I would hate to have to duplicate the props like that. – Manolo Santos Aug 25 '16 at 14:23
  • 3
    There is no native solution. You could use [lodash's `_.pick` function](https://lodash.com/docs#pick) though. – nils Aug 25 '16 at 14:25
  • 3
    The so-called "extended dot notation" proposal allows you to say `object.{a, b, c}`, to "pick" `a`, `b`, and `c` from `object` and create a new object from them. This has been discussed on the es-discuss mailing list. See [this github repo](https://github.com/rtm/js-pick-notation/blob/master/minimal/spec.md). –  Aug 25 '16 at 14:39
  • @torazaburo nice work! I imagine, it's not ready to be transpiled though? – nils Aug 25 '16 at 14:45
  • Just FYI, [object rest properties](https://github.com/sebmarkbage/ecmascript-rest-spread) is not a part of ES 6. It's currently an ES proposal in stage 2. – Michał Perłakowski Aug 25 '16 at 15:19
  • 1
    I really think it would be a pity if the assignment operator ever evaluated to anything other than its right operand. – Paul Aug 25 '16 at 15:25
  • @Paulpro Good point! It would be confussing. In any case, transitive destructuring is rather confusing itself (e.g. `const { a, b } = { c, d } = { a:1, b:2, c:3, d:4 };`) – Manolo Santos Aug 25 '16 at 15:33
  • I don't find that confusing. It acts exactly like I would expect: `const tmp = { a:1, b:2, c:3, d:4 }; const { c, d } = tmp; const { a, b } = tmp;` – Paul Aug 25 '16 at 15:36
  • Or: `const sanitized = ( ({a, b, c}) => ({a, b, c}) )(original);` – trincot Aug 25 '16 at 15:39
  • @Paulpro You are precisely right. There were some proposals for doing this using destructuring which suffered from precisely that problem. –  Aug 25 '16 at 16:47

2 Answers2

3

For this purpose I use 2 helper functions

export const pickProps = (object, ...props) => (
  props.reduce((a, x) => {
    if (object.hasOwnProperty(x)) a[x] = object[x];
    return a;
  }, {})
);

export const omitProps = (object, ...props) => {
  const no = {...object};
  props.forEach(p => delete no[p]);
  return no;
};

You can also do

const original = {
  a: 1,
  b: 2,
  c: 3,
  evil: "evil",
  ugly: "ugly",
};

const { a, b, c } = original;
const filtered = { a, b, c };
Dmitriy Nevzorov
  • 5,697
  • 1
  • 18
  • 28
  • Yeah, I am using somethin like `pickProps` right now, but I wanted to know if there is direct syntax support for this case. – Manolo Santos Aug 25 '16 at 14:55
0

I don't think your way to "blacklist" is good, because it unnecessarily assigns original.evil to evil, and original.ugly to ugly.

You can try this approach:

const blacklistFilter = (obj, blacklist) => Object.entries(obj)
  .filter(([key, value]) => !blacklist.includes(key))
  .reduce((obj, [key, value]) => (obj[key] = value, obj), {})

const whitelistFilter = (obj, whitelist) => Object.entries(obj)
  .filter(([key, value]) => whitelist.includes(key))
  .reduce((obj, [key, value]) => (obj[key] = value, obj), {})

const original = {
  a: 1
 ,b: 2
 ,c: 3
 ,evil: 'evil'
 ,ugly: 'ugly'
}

console.log(blacklistFilter(original, ['evil', 'ugly']))
console.log(whitelistFilter(original, ['a', 'b', 'c']))

Object.entries() returns an array of object's keys and corresponding values in format [key, value], the filter() method filters keys based on whether they aren't blacklisted or whether they are whitelisted, and reduce() method converts the [key, value] array back to an object (similar method as in this answer).

Community
  • 1
  • 1
Michał Perłakowski
  • 70,955
  • 24
  • 137
  • 155