0

I could make great use of a type like Extract<T> that would resolve to all the types in a union T that are NOT string or number. Is this possible?

Extract<T, string | number> will find everything that extends string or number, but I want something that will extract everything this is not exactly string or number.

Some examples of what I want, if the type was named NonIndex:

NonIndex<string | 1>; // expect 1
NonIndex<'a' | 1>; // expect 'a' | 1
NonIndex<string | 'a'>; // expect 'a'

Here is the closest I've come in the playground, but you can see it's not right yet. Can it be done?


My motivating use case is to (substantially) improve the typing of omit() in one of my open source libraries. If you're very brave, you can take a look at my almost solution here. But beware, it has a lot of typing going on! Everything is working well except the last line, as commented at that link.

Eric Simonton
  • 5,702
  • 2
  • 37
  • 54
  • Why do you want this? Is it the use case mentioned in [this question](https://stackoverflow.com/questions/61047551/typescript-union-of-string-and-string-literals) or [this question](https://stackoverflow.com/questions/51954558/how-can-i-remove-a-wider-type-from-a-union-type-without-removing-its-subtypes-in)? If not, then could you elaborate? Maybe there is an alternate solution to your underlying issue. – jcalz Nov 24 '20 at 02:13
  • Sure. I updated my question to include my actual use case. But it's kind of a lot to take in! – Eric Simonton Nov 24 '20 at 02:48
  • 1
    It looks like you want `KnownKeys` from the second linked question, but the specifics of your `omit` are a bit more than I'd like to wade into. Note how you can use `KnownKeys` to build a different version of `Omit` that behaves a little better in the face of indexable types, like [this](https://tsplay.dev/rNdpYN). Whether your use case wants `Omitʹ` to remove all *known* string literal keys from `T` as well as any string index is up to you, but you can use `KnownKeys` to do so. If this constitutes an answer for you I'll write it up. – jcalz Nov 24 '20 at 15:33
  • Thank you for the pointers and the playground! I'd like `Omit'` to leave indexed keys alone (omitting a single, unknown string from `Record` is still `Record`) and turn known parameters optional (since we don't know which one might have been omitted, if any). Omitting a known string should also leave indexed keys alone, but remove a matching known key. I'll work with what you gave me so far tonight to see if I can make it come together! – Eric Simonton Nov 24 '20 at 22:29
  • I tried for a while to get things working, and I learned in the process. Thank you for helping! In the end I decided to compromise and not support the rarest use cases because I just couldn't figure them out. – Eric Simonton Nov 26 '20 at 14:00

1 Answers1

2

I believe you will always be hobbled by the fact that for

type x = string |  'a' 

x simply resolves to string because 'a' is just a specialization of string

The same applies for

type y = number | 1

(i.e. y is just number)

This makes your last case impossible to solve because:

NonIndex<string | 'a'>

is interpreted by the type-system as being simply:

NonIndex<string>

i.e. all knowledge of 'a' is lost and it, therefore, cannot be extracted.

NOTE:

Types T and U can be considered equal if T extends U and U extends T.

I thought I'd nailed it with something along the lines of:

type StrictInvExtract<T, U> = T extends U ? (U extends T ? never : T) : T;
type NonIndex<T> = StrictInvExtract<T, string>;

but the failure of the last case made me realise all approaches to this are a dead-end.

spender
  • 117,338
  • 33
  • 229
  • 351