Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: add type guard objectHas #48

Open
tychenjiajun opened this issue Mar 21, 2022 · 3 comments
Open

Proposal: add type guard objectHas #48

tychenjiajun opened this issue Mar 21, 2022 · 3 comments

Comments

@tychenjiajun
Copy link
Contributor

In #22 we added objectHasOwn. It's a type guard with return type to be object is (ObjectType & Record<Key, unknown>)

microsoft/TypeScript#21732 suggested a similar type guard inOperator which has exactly the same return type of objectHasOwn. However, inOperator can assert property existence in prototypes while objectHasOwn can not.

I propose a new function objectHas here, which should have the same implementation as inOperator. The naming changed to objectHas to align with objectHasOwn.

Proposing implementation

export function objectHas<ObjectType, Key extends PropertyKey>(
	object: ObjectType,
	key: Key,
): object is (ObjectType & Record<Key, unknown>) {
	return key in object;
}

What's the difference between objectHasOwn and objectHas?

See https://tc39.es/ecma262/multipage/abstract-operations.html#sec-hasproperty and https://tc39.es/ecma262/multipage/abstract-operations.html#sec-hasownproperty

What's the difference between this issue and #47?

#47 proposing a new function keyIn that has the exact same function body but a different type definition and return type guard. They result in different type predicates. See microsoft/TypeScript#43284 (comment)

When to use keyIn, native in operator and objectHas

in operator

Native in operator is good at narrowing in union types. For example, #30 can be resolved by just using the in operator.

const a: PromiseSettledResult<string> = { reason: '1', status: "rejected" }
if ('reason' in a) {
    // a is now PromiseRejectedResult
}

Playground

(But be careful that promiseSettledResults.filter(p => 'reason' in p) won't work as expected right without defining your own isRejected type guard.)

keyIn

keyIn should be used when you want to narrow the type of key to specific literals.

const a = 'foo';

const obj = {
    foo: 1
}

if (a in obj) {
    // a is literal type 'foo' now
}

Playground

objectHas

objectHas should be used when you want to safely check the existence of a property? I'm still confused about the use cases of this function.

@tychenjiajun
Copy link
Contributor Author

Any thoughts? @younho9 @sindresorhus @jonahsnider

@jonahsnider
Copy link
Contributor

Maybe it's just me, but, I don't see much utility in any of these functions other than objectHasOwn. I find that it can be very easy to make mistakes in the left-hand side of the in operator, especially when a property is renamed. I prefer to use discriminated unions whenever possible in my own code.

@sindresorhus
Copy link
Owner

I personally would not use objectHas either as I would not use foo in object. I only use objectHasOwn. Another concern with objectHas is that it's easy to type, so a user might reach for it without realizing objectHasOwn is better in most scenarios. So thinking more about this, if we were to add such a utility, it needs a more verbose name.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants