Skip to content

fix: objectKeys should always return subset of string - #42

Open
max-programming wants to merge 1 commit into
garronej:mainfrom
max-programming:fix/objectKeys-key-string
Open

fix: objectKeys should always return subset of string#42
max-programming wants to merge 1 commit into
garronej:mainfrom
max-programming:fix/objectKeys-key-string

Conversation

@max-programming

Copy link
Copy Markdown

Fixes #26

image

@garronej

Copy link
Copy Markdown
Owner

Hello @max-programming,

Thanks for the PR. Good catch, there is a bug indeed however the PR does not adress the core of the issue.
The core of the problem is that keyof { [key: string]: unknown }; is string | number and keyof { 43: uknown; } is 42.
So the typeof operator does not reflect what are returned type of Object.keys() (string).

What we need is to convert number into string and any specific number like literal 42 into literal "42".

What you are doing instead is to just strip away from the return type any keys that would be declared as a number and this isn't correct.

Let me know if you are interested in solving this little typeing challenge. If you do, make sure you write the correct type level test in tests/objectKeys.types.ts, if you can write the test clearly. ChatGPT is usually able to solve the type gymnastic.

@max-programming

max-programming commented Oct 28, 2025

Copy link
Copy Markdown
Author

@garronej Yea I agree that the return type should include the literal keys. But the issue here is that the type that is passed in through generic is of { [key: string]: unknown } rather than the usual { 42: unknown; }

So either we have to override the passed in type using as const, manually give the type through type argument, or we have to live with this as a fallback. Because in the end it's up to the user how they give the types.

If I as a consumer, make an object of let's say type Record<string, unknown> (equivalent of the [key: string] type), it basically means that the key can be any string instead of me directly making an object

Here's an example:

const obj = {
  someKey: "someValue"
} // Type has key as "someKey" (inferred)

const obj2: { someKey: unknown } = {
  someKey: "someValue"
} // Type has key as "someKey" (manually set)

const obj3: Record<string, unknown> = {
  someKey: "someValue"
} // Type has key as any string (manually set)

const o1Keys = objectKeys(obj) // ["someKey"]
const o2Keys = objectKeys(obj2) // ["someKey"]

// No way to get the keys if the type is too flexible
const o3Keys = objectKeys(obj3) // string[] (better than having (string | number)[])

// If manually given the type
const o3KeysBetter = objectKeys<{ someKey: unknown }>(obj3) // ["someKey"]

// Best approach
const o3KeysMuchBetter = objectKeys(obj3 as const) // ["someKey"]

I think we should keep the default string[] for Record and the { [key: string] } kinda types, but we should specify in the docs to use as const in case their type is like this

Let me know if this sounds like a reasonable solution

ps: i typed all this on mobile and will update in another comment if this as const approach really works, but I believe it should

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

Successfully merging this pull request may close these issues.

objectKeys should always return subset of string

2 participants