Search code examples
typescripttypescript-generics

TypeScript - Type Narrowing In Generics Not Working


I am working on a project and have a need to do some custom schema work. I have a type of MyModel and am trying to extract all properties that inherit from SchemaIdentity<any> and make those properties into a union using ExtractIdentity<>.

I have ExtractIdentity<MyModel>, but it is extracting all properties (id, name), not the ones that inherit from SchemaIdentity. The result should be id What am I doing wrong?

I have tried changing [K in keyof R]: R[K] extends SchemaIdentity<any> ? K : never; and it seems like narrowing is not working as expected. I would expect any R[K] that inherits from SchemaIdentity to return is property name of K. The name property on MyModel has a type of SchemaDefault<string> yet it's some how matching the type narrowing.

NOTE: I have omitted code in the classes to keep the example smaller.


export abstract class SchemaBase<T extends any>  {

}

export class SchemaDefault<T extends any> extends SchemaBase<T>  {


}

export class SchemaNumber<T extends number = number> extends SchemaBase<T> {

}

export class SchemaIdentity<T extends string | number> extends SchemaBase<T> {

}

export class SchemaDefinition<T extends {}> extends SchemaBase<T> {

}

export type ExtractIdentity<T> = T extends SchemaDefinition<infer R> ? {
    [K in keyof R]: R[K] extends SchemaIdentity<any> ? K : never;
}[keyof R] : never;

type MyModel = SchemaDefinition<{
    id: SchemaIdentity<number>;
    name: SchemaDefault<string>;
}>


type MyIds = ExtractIdentity<MyModel>

Playground Here

TypeScript Version: 5.6.3


Solution

  • The issue you do have is that all types matches even when you use SchemaNumber which is slightly diferent but reduce to the same type

    If you try this:

    type test1 = SchemaNumber<any> extends SchemaIdentity<any> ? true : false
    //         ^? false       
    type test2 = SchemaIdentity<any> extends SchemaNumber<any> ? true : false
    //         ^? true
    

    What you can ses is that SchemaIdentity extends even SchemaNumber and further more SchemaDefault which takes any as a generic

    On that matter try to avoid any and you can alway avoid them use unknowninstead.

    So for it to work you need to have differents types where extends does not match.

    If SchemaIdentity would take a string and only a string then it would work agains SchemaNumber but still not agains SchemaDefault.

    Another way to make it work is to have different type - in this case have diffent properties / methods etc.

    So if you want to keep the generic types simple code your class and it will work - except if all properties match as well.

    I hope it helps