Object.{pick,omit}

What is the side effect? Jut due to the keyword? I have understood it, and perhaps it is not a good way to define on Object.prototype directly

in any case, please define in Object but not is Object.prototype

I get it, Object is so fundamental, thanks a lot. I will modify the specification tomorrow.

It is not proper to redefine Object.prototype because Object is especially fundamental, which may have side effects of any other objects inherited from it.

So change the proposal and define it on Object directly and update the specification at the same time:

Object.pick / Object.omit

Object.pick({a : 1, b : 2}, ['a']); // => {a : 1}
Object.omit({a : 1, b : 2}, ['b']); // => {b : 1}

Object.pick({a : 1, b : 2}, ['c']); // => {}
Object.omit({a : 1, b : 2}, ['c']); // => {a : 1, b : 2}
  1. __proto__ and prototype problem?
    hope direct throw an error.
    see Lodash search issues: __proto__
    see Lodash search issues: prototype

  2. always returns plain object?

  3. Symbol problem?
    e.q: Object.pick([], [Symbol.iterator]); // here expect is ?

const foo = Object.defineProperty({}, 'bar', {
    get() { throw new Error() }
})
Object.pick(foo, ['bar']) // here expect is ?

how should this error stack be designed?

  1. passthrough?
  2. wrapping original error?

If we take the implementation above, the error stack will be for the snippet:

const pick = (obj, keys) => Object.fromEntries(
    keys.map(k => obj.hasOwnProperty(k) && [k, obj[k]]).filter(x => x)
);

pick(Object.defineProperty({}, 'bar', {
    get() { throw new Error() }
}), ['bar']);

will be:

Uncaught Error
    at Object.get (<anonymous>:6:19)
    at <anonymous>:2:51
    at Array.map (<anonymous>)
    at pick (<anonymous>:2:10)
    at <anonymous>:5:1

But if implemented in native, the error may be able to pass through.

When it comes to the question, should the method pick up such an object, some properties of which will throw an error? I think it should not as the object can't be accessible in some way.

It depends on whether properties of prototype can be picked:

If can:

Object.pick([], [Symbol.iterator]); // => {Symbol(Symbol.iterator): f}

otherwise:

Object.pick([], [Symbol.iterator]); // => {}

Any progress?

My opinions about some of the posed questions:

  • It should be possible to pick symbols off of an object. I can't see any argument against this functionality.
  • It should always return a plain object.
  • As for the prototype key, I'm not sure what the specific concerns are. The "prototype" key only has special significance when it's attached to a function, correct? And the newly created object won't ever be a function, so there shouldn't be any problem picking a property named "prototype" to a new object.
  • __proto__ is a more difficult one, but I think it makes the most sense to be consistent with how other object-creation functions deal with it. I noticed that Object.fromEntries() won't trigger the __proto__ setter, it instead create an own __proto__ property on the new object. I think this behavior makes the most sense.
  • I would prefer that it isn't possible to pick inherited properties. I think it makes the most sense to tailor this function's use case towards people who are trying to use plain objects as a mapping. In such a scenario, the programmer might want to pick an array of keys off of a mapping (or, any object really), where those keys come from an untrusted data source. The programmer shouldn't have to worry about the scenario where "toString()" ended up in the array of keys, and now the new object has an own toString value that the original object was only inheriting.

I have commited additonal descriptions on FAQ, and feel free to discuss around them.

1 Like

I would like to hear some other people's opinions on picking properties off of the prototype.

I mentioned my own above (I prefer not being able to pick off of the prototype, as it makes it easier to work with untrusted/unknown keys).

In the FAQ, @aleen42 noted the current plans are to follow in Lodash's footsteps and allow picking off properties on the prototype. I can see the value in this decision. One might expect that if they could get a value by doing obj['toString'], then they could also pick that value by doing Object.pick(obj, ['toString']). I'm not sure how often this behavior would be useful though, especially since prototypes usually just contains functions that operate on the object they're attached to, and wouldn't work right when picked and called out of context - though this certainly isn't always the case, and I'm sure there are use cases for this kind of behavior.

Personally, I would expect pick/omit to go over only own properties. From my own experience, I use objects like containers of data, and I wouldn't want to worry about prototype or not (which can expand over time, depending on the type of object, e.g., forEach being added to NodeList, and this could surprise me in the future).

As an in-between, this could be perhaps implemented as the third argument with options. As a hot take here,

Object.pick(({ a: 1, b: 2 }, ['a', 'toString'], { fromPrototype: true }); // => { a : 1, toString: [native] }

// it would default to `false`
Object.pick(({ a: 1, b: 2 }, ['a', 'toString']); // => { a : 1 }

If you're explicit about the property name, it should pull from the prototype. This would be comparable to something like spread vs destructuring

let o = Object.create({ pro: 1})
{...o} // {}
let { pro } = o // pro = 1

And if not pulling from the prototype, it should be an "own" method, a la getOwnPropertyDescriptor

Object.pick({}, ['toString']) // { toString }
Object.pickOwn({}, ['toString']) // {}
1 Like

@aleen42 i shall renamed the thread title to Object.{pick,omit}.

Thanks a lot. I have no privileges to edit the title before.

I assume you're suggesting that we rename "pick" to "pickOwn" here? To be more consistent with other "own" methods? Or are you suggesting that we provide both "pick" and "pickOwn"?

I would also like to hear some opinions about the inclusion of Object.omit(). I agree with @voxpelli's point that omit can already be done without much work, as follows:

const { a, ...foo } = { a: 1, b: 2 }; // => foo becomes {b : 1}

This of course doesn't allow one to provide an arbitrary list of attributes to omit, but I've never run into a situation where I've needed such behavior. Has anyone else? How useful would an omit function really be? I would be fine dropping that one and just focusing on Object.pick().

Not exactly. Given the explicit naming for what's picked, I would expect it to pull from the prototype and the name name pick would be appropriate as is.

I, too, have needed pick more than omit. And while I have used destructuring to rest in the past, the problem is that you're also polluting the scope with unintentional identifiers which have to be renamed if there are any conflicts. So you might end up with something like

let a = 'I use this'
const { a: _, ...foo } = { a: 1, b: 2 }; // _ is now taking up space in scope

I think its a nice compliment to include along with pick(), so I'm not opposed to including it. And it would have advantages over using a rest.

Of course I'd much rather have a pick syntax over either of these Object methods.

1 Like

Ah, I understood your argument wrong. I thought you were implying that Object.pick() wasn't "explicit" about property names, because you wouldn't always explicitly spell out which property names to pick - they could be programmatically generated and passed into Object.pick(). But, it seems you were talking about "explicit" vs "implicit" in a slightly different sense, where as long as the property name is provided (programmatically, or through syntax/string literals), then you're explicitly wanting that property, and it should come off of the prototype if needs be.

1 Like