-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Support for Object.hasOwn
(lib.d.ts
and narrowing)
#44253
Comments
Discussed a bit with @jamiebuilds offline. Some key scenarios and questions to resolve: declare const ra: Record<string, any>;
if (Object.hasOwn(ra, "foo")) {
ra.foo; // should be 'any'
}
declare const ru: Record<string, unknown>;
if (Object.hasOwn(ra, "foo")) {
ra.foo; // should be 'unknown'
}
declare const abcd: { a: string, b: string } | { c: string, d: string };
if (Object.hasOwn(abcd, "a")) {
abcd.b; // should be OK
}
// Should this be OK?
// Could argue either way
Object.hasOwn(abcd, "efg")
// TODO: Add more use cases + desired behavior |
Should there be a separate issue for |
Object.hasOwn
in lib.d.ts
Object.hasOwn
in lib.d.ts
and narrowing
Object.hasOwn
in lib.d.ts
and narrowingObject.hasOwn
(lib.d.ts
and narrowing)
I don't like it, but we already did it for I think one concern I have is making sure that the declare const abcd: { a: string, b: string } | { c: string, d: string };
if ("a" in abcd) {
abcd.b; // okay
}
else {
abcd.c // okay
abcd.d // okay
}
|
|
Any update? P.S. On 17th October Node.js 17.0 will be released. |
It's not supported by TypeScript yet: microsoft/TypeScript#44253
Since there doesn't seem to be much progress here, could I help with this in any way? |
This feature is, AFAIK, now Stage 4 and scheduled to be part of this June release of ES2022. It would be wonderful if it could do narrowing, because I wouldn't be surprised if it will eventually replace most usages of Object#hasOwnProperty(hasOwn is safer) and the |
fp-ts has a totally good implementation: https://github.com/gcanti/fp-ts/blob/2.11.9/src/ReadonlyRecord.ts#L249 |
I'm using this, I suggest others try it out and if there are no issues, we merge this baby :) export const hasOwn = <RecordKeys extends string>(
record: Readonly<Record<RecordKeys, unknown>>,
key: string,
): key is RecordKeys => {
return Object.prototype.hasOwnProperty.call(record, key)
} (PS I basically copied from fp-ts |
@devinrhode2 That approach has a lot of flaws This is the current definition I'm using: export type SetRequired<BaseType, Keys extends keyof BaseType> =
BaseType &
Omit<BaseType, Keys> &
Required<Pick<BaseType, Keys>>;
interface ObjectConstructor {
hasOwn<BaseType, Key extends keyof BaseType>(record: BaseType, key: Key): record is SetRequired<BaseType, Key>
hasOwn<Key extends PropertyKey>(record: object, key: Key): record is { [K in Key]: unknown }
} Edit: More complete cases |
hmm... not working in the one case I'd like it to work let role = 'adsf' as string
const rolesConst = {
'admin': 'admin',
'user': 'user'
} as const
if (!Object.hasOwn(rolesConst, role)) {
throw new Error('Unknown role: ' + role)
}
role // Should be `keyof typeof rolesConst`, not `string` :)
// assert<IsEqual<typeof role, keyof typeof rolesConst>>() |
@devinrhode2 If you make |
It's a String coming over the wire, so neither of those options work :( The goal is to verify the given role is known, to act as a type guard, to narrow the type |
Maybe the fp-ts version should require the object to look like a const (have "readonly" modifiers) And it doesn't look like a const, recommend using your version? How could we get the best of both worlds?! |
This allows me to both cleanup type-guards before calling |
Is there a benefit to using |
When I'm passing a parameter into this util, the type is I recall trying to use unknown first but maybe I was getting a type error on the return type, that unknown was not assignable to Property keys iirc? Or maybe on hasOwnProperty.call... |
Here's a playground with a possible implementation. |
Just for good measure, I really just copied source from fp-ts project. Actually the type guard there was originally implemented in this PR: gcanti/fp-ts#1075 by @gcanti As such I think it'd be good to seek a "thumbs up" or "lgtm" from @gcanti before this slips into core :) |
@jamiebuilds is there a better way to write the code snippet I provided?
I did experiment briefly with a const array approach but opted to go with const object instead because i liked being able to write code like: if (role === rolesConst.admin) { ...yay } As opposed to: if (role === rolesConst[2]) { ...ew } Maybe this is an edge case where a ===== |
ES2022 has |
I'm happy to PR in this implementation (i added my narrowing implementation on top of the current base) if the maintainers are wanting it. |
@nicolas377 doesn't pass as many test cases as my last example But, idk how valuable these test cases are, it's been a minute, and I didn't originally write them. Regardless, I assume we want to get as many of those passing as possible. When I swap in your implementation, it goes from 8 failing tests to 27 :( |
Wouldn't we need something like better excess property checks on unions here otherwise you could do: const result: { a: string } | { b: string, c: string } = { a: '', b: '' }
if (Object.hasOwn(result, "b")) {
result.c; // should NOT be ok
} Although it seems to mention already in regards to |
Try this one: /** Extract from T those types that has K keys */
type ExtractByKey<T, K extends keyof any> =
T extends infer R
? K extends keyof R
? R
: never
: never
type KeyofUnion<T> = T extends infer R ? keyof R : never
declare global {
interface ObjectConstructor {
/**
* Determines whether an object has a property with the specified name.
* @param o An object.
* @param v A property name.
*/
hasOwn<T extends Record<keyof any, any>, K extends keyof any>(
o: T,
v: K
): o is K extends KeyofUnion<T> ? ExtractByKey<T, K> : T & { [P in K]: unknown }
}
} If you want the key to be required: type RequiredByKey<T, K extends keyof T> = { [P in K]-?: T[P] } & { [P in Exclude<keyof T, K>]: T[P] }
type ExtractAndRequiredByKey<T, K extends keyof any> =
T extends infer R
? K extends keyof R
? RequiredByKey<R, K>
: never
: never
declare global {
interface ObjectConstructor {
hasOwn<T extends Record<keyof any, any>, K extends keyof any>(
o: T,
v: K
// @ts-expect-error I don't know how to fix this error 😥
): o is K extends KeyofUnion<T> ? ExtractAndRequiredByKey<T, K> : T & { [P in K]: unknown }
}
} |
TypeScript doesn't support it yet: microsoft/TypeScript#44253
So to clarify to myself and others a thing or two:
So, this hasn't been revisited for 2.5 years and probably won't be for the foreseeable future (it's a very nuanced and tricky thing to solve correctly, I'm not blaming anyone). So what does that leave us with, what's the best practice?
|
Totally guessing, but it's probably due to its unique syntax
…On Tue, Dec 12, 2023 at 5:36 AM F. Levi ***@***.***> wrote:
So to clarify to myself and others a thing or two:
- this is strongly related to #41915
<#41915> as
Object.prototype.hasOwnProperty()
<https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty>
is an older and less safe version of Object.hasOwn()
<https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn>
.
- the less performant and still potentially unsafe version, the in
<https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in>
operator (which might still have its very limited and rare use cases, but
in a modern project it should almost never be preferred over the top ones)
has a narrowing implementation in TypeScript, so people will most likely
only use the in version because of this in TS projects
- how is in considered good enough to have a narrowing type but the
others aren't? Is the in operator actually type safe? Or did it get
its narrowing functionality back when the devs of TS didn't know any better?
—
Reply to this email directly, view it on GitHub
<#44253 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAEDZKHBJZUKPTIXAL253SDYJA6S5AVCNFSM45P6SZZ2U5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TCOBVGE4DMNJYGM2A>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
It's been a hot second since I've touched typescript, but looking back through this issue, I think it'd be great to have an open discussion about what Object.hasOwn should look like in ts. It's obviously becoming more and more popular, and the current implementation leaves almost everything up to the end user for them to cast narrow, so I think narrowing should be part of the core implementation of hasOwn. I have two questions:
p.s. I'm a senior in high school, and I won't be working on this a lot come spring semester, so I'd love for a maintainer to pick this up before I disappear back into my academic hole, so I'm mostly trying to kick this discussion off, because I'd love to see hasOwn supported. |
// https://github.com/microsoft/TypeScript/issues/1260#issuecomment-1288111146
// Creates a union of all keys of all objects in the Terface union
type AllKeys<Terface> = Terface extends any ? (keyof Terface & (string | number | symbol)) : never;
// Creates a new interface adding the missing keys to Terface
type Wrap<Terface, Keys extends string | number | symbol> = (Terface & {
[K in Exclude<Keys, keyof Terface>]?: undefined;
});
// Distributes the union and automatically add the missing keys
type NicerUndefineds<Terface, Keys extends AllKeys<Terface> = AllKeys<Terface>> = Terface extends any ? Wrap<Terface, Keys> : never;
declare global {
interface ObjectConstructor {
hasOwn<
K extends string|number|symbol,
O extends Record<any, any>,
OK extends NicerUndefineds<O> extends { [k in K]?: infer VType }
? { [k in K]: VType } & O : { [k in K]: unknown } & O,
>(obj: O, k: K): obj is OK;
}
} I'm seriously tired of all of these. Finally I can proceed with my day. (I forgot what I was originally working on) Thank you too. |
any progress on this? |
I believe this awaits someone's PR, right? |
Yes, we're awaiting a PR. This is a pretty complicated problem to solve, as you may have noticed from the discussions above. There are a couple of proposed solutions, but I think it'd be a good idea to gather use/test cases for |
Actually "no thank you" to me. My code doesn't satisty most of the requirements demanded here. My latest tests confirm that the second implementation by @ziloen above makes all of the tests happy. Tested every case stated above. Also tested a rather large codebase in our project. All works fine. Last question would be, how to solve the sad |
There is yet no PR addressing the issue? Object.hasOwn(obj, "prefix") && obj.prefix // <---- still not being inferred as a valid key |
Our use case for From a secure coding perspective, I might suggest that as a general rule |
Object.hasOwn(obj, key)
has just moved to stage 3.https://github.com/tc39/proposal-accessible-object-hasownproperty
The text was updated successfully, but these errors were encountered: