#Help understanding difference between "Extract<keyof MyObject, string>" and"keyof MyObject & string"

24 messages · Page 1 of 1 (latest)

timber lantern
#

Hi,

Can someone help me understand why one of my types below works fine, but the other causes an error?
My understanding is these should be functionally the same, but I'm clearly missing something.

The error I receive on the broken one is:

Type 'keyof MyObject & string' cannot be used to index type '{ [K in keyof MyObject as K extends string ? K : never]: { column: K; op: "=" | "!=" | "<" | "<=" | ">" | ">="; value: MyObject[K]; }; }'

Both are filtering the keys of MyObject to just the string keys, but TS is moaning at me for one of them.

Any help understanding this would be great please.

export type WhereTypeWORKING<MyObject> = {
  [K in Extract<keyof MyObject, string>]: {
    column: K;
    op: "=" | "!=" | "<" | "<=" | ">" | ">=";
    value: MyObject[K];
  };
}[Extract<keyof MyObject, string>];

export type WhereTypeBROKEN<MyObject> = {
  [K in keyof MyObject as K extends string ? K : never]: {
    column: K;
    op: "=" | "!=" | "<" | "<=" | ">" | ">=";
    value: MyObject[K];
  };
}[keyof MyObject & string];

An example showing the same output from both methods of extracting string keys

type Table = {
  id: number;
  name: string;
  42: boolean;
  [Symbol.iterator]: () => Iterator<any>;
};

// both equate to "id" | "name"
type test = keyof Table & string
type test2 = Extract<keyof Table, string>

Many thanks in advance!

warm iris
#

this is only an issue because MyObject is generic here, so ts can't evaluate the expressions.

timber lantern
#

does it not infer its an object from the keyof?

warm iris
#

no, not at all, but that's not the issue

#

adding an extends object clause on the generic doesn't change the result

timber lantern
#

if i tried to pass it a non-object it would complain though?

warm iris
#

no, it'd work just fine

#

object vs non-object is completely irrelevant here

timber lantern
#

ooh ok

#

I'm not sure I understand why the bottom example equates the same, but isn't allowed though, sorry

warm iris
#

for any type, Extract<keyof T, string> and keyof T & string would evalutate to the same result
but ts can't evaluate those when T doesn't exist yet

in the working example, the keys from the mapped type and the indexer are trivially equivalent
you could use keyof MyObject & string instead in both positions and it'd also work, because again, trivially equivalent
but that's not the case with the remapping clause

#

if MyObject wasn't generic, ts could evaluate the mapping and see that the types are actually equivalent:

gleaming kelpBOT
#
that_guy977#0

Preview:```ts
type MyObject = {
id: number
name: string
42: boolean
[Symbol.iterator]: () => Iterator<any>
}

export type WhereTypeWORKING = {
[K in Extract<keyof MyObject, string>]: {
column: K
op: "=" | "!=" | "<" | "<=" | ">" | ">="
value: MyObject[
...```

warm iris
#

but because MyObject doesn't exist yet, ts can't do that

timber lantern
#

with this bit:

or any type, Extract<keyof T, string> and keyof T & string would evalutate to the same result
but ts can't evaluate those when T doesn't exist yet

does that mean there is a possible value of T that wouldn't satisfy both?

warm iris
#

no

#

ts just doesn't have the logic to say Extract<K, string> and K & string and K extends string ? K : never yield the same result

timber lantern
#

oooh ok, thanks!
Do you know anywhere I can read more on this?
I've been through about 4 different typescript courses now, finish each one feeling confident, then immediatly hit a wall when implementing into my codebase lol

warm iris
#

read more on.. what exactly?

timber lantern
#

so I would know why TS doesnt know they will always equate to the same.

I know now that that TS doesnt know it, but i dont get "why" it doesnt if that makes sense?

warm iris
#

it's just not implemented, it'd be insanely complex and impossible to maintain

#

K extends number ? never : K would be the same as the above 3 if K was restricted to string | number

#

ts doesn't know this because of practical limitations, not because it's a design choice

timber lantern
#

fair enough, I definitely understand more now than I did 10 mins ago.
Thanks a bunch for taking the time to explain to me