skip to Main Content

I’m trying to create a custom TypeScript type that only accepts field names whose types are related to a specific base class, WhateverBaseClass.

How can I modify the MyType definition to ensure it only includes the keys of MyClass that correspond to fields of type WhateverBaseClass or arrays of WhateverBaseClass?

Here’s a simplified version of my code:

class WhateverBaseClass {}

class Invalid {
  public invalidName!: string;
}

class Valid extends WhateverBaseClass {
  public validName!: string;
}

class MyClass {
  public name!: string;              // Invalid: string
  public validArray!: Valid[];       // Valid: array of Valid
  public validField!: Valid;         // Valid: instance of Valid
  public invalidField!: Invalid[];   // Invalid: array of Invalid
}

type MyType<T> = {
  [K in keyof T]: T[K] extends WhateverBaseClass | (WhateverBaseClass[]) ? T[K] : never;
}[keyof T];

// Expectation: 
const expectation: MyType<MyClass>[] = ["validArray", "validField"]; // Must only accept these two

// Result: 
const rs: MyType<MyClass>[] = ["validArray", "validField", "name", "invalidField"]; // But it accepts all

2

Answers


  1. If you want an array of the keys your code needs to be corrected a bit:

    type MyType<T> = {
      [K in keyof T]: T[K] extends WhateverBaseClass | (WhateverBaseClass[]) ? K : never;
    }[keyof T];
    

    Inside the code I replaced T[K] by K

    Then it looks like the code is not working but it’s because you didn’t put anything in WhateverBaseClass, so typescript seems to consider that any class extends it.

    The code starts to work as expected as soon as I put some properties in the base class.

    class WhateverBaseClass {
       public pleasework: boolean = true;
    }
    

    Working playground here

    Login or Signup to reply.
  2. Your question could translate to:
    "I want a type that represents the union of all property name in a type for which the property type matches a constraint"

    The constraint here seems to be extends Valid | Valid[].

    First we can pick by type:

    type PickByType<Source, Type> = {
      // if type of Source[k] does not match Type then give never key to filter k
      [k in keyof Source as Source[k] extends Type ? k : never]: Source[k]
    }
    

    It will create a new type that only accepts the properties you have targeted by their type.
    PickByType<{n: number, s: string}, number> => {n: number}

    Since in your case you only want the keys you can write MyType like that:

    type MyType<T> = keyof PickByType<T, Valid | Valid[]>
    

    playground

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search