Home > database >  How to dynamically set a value of a specific type using generics in Typescript
How to dynamically set a value of a specific type using generics in Typescript

Time:04-06

Suppose I have a generic utility class that is supposed to interface with a class and has a method setPropertiesToTrue that takes in a list of class properties, and sets those properties inside the instance of the class to true.

class SomeObject{
  x: boolean 
  y: boolean
  z: string

  constructor(x: boolean,y: boolean,z: string){
    this.x = x,
    this.y = y,
    this.z = z
  } 
}

class ProperySetter<TSomeObject>{
  objectContext: TSomeObject

  constructor(objectContext: TSomeObject){
    this.objectContext = objectContext
  }

  setPropertiesToTrue(properties: Array<keyof TSomeObject>){
    properties.forEach(property => {
      this.objectContext[property] = true
    })
  }
}


const someObject = new SomeObject(false, false, "hello")

const propertySetter = new ProperySetter(someObject);

propertySetter.setPropertiesToTrue(["x", "y"])

console.log(someObject.x) // changed from false to true
console.log(someObject.y) // changed from false to true
console.log(someObject.z) // unchanged


Here is a TS Playground example of more or less what I'm trying to do.

In the example above, (at line 22 in the playground), it appears typescript has no the idea that keys being passed as properties correspond to boolean values inside the generic TSomeObject. So I’m getting the error:

Type 'boolean' is not assignable to type 'TSomeObject[keyof TSomeObject]'.

And this makes sense because keyof TSomeObject correspond to all keys of TSomeObject not just the keys corresponding to the boolean keys.

Is there a way to get around this?

CodePudding user response:

You can strengthen the constrain of the input array to include only the keys that are boolean values. However, TS is not smart enough to see the know that the keys are valid in the array. Therefore, you're going to just have to go with a cast to get the code to work. Here is an example of the working code with the better constraints on the keys type:

class SomeObject{
  x: boolean 
  y: boolean
  z: string

  constructor(x: boolean, y: boolean, z: string){
    this.x = x,
    this.y = y,
    this.z = z
  } 
}

class ProperySetter<TSomeObject>{
  objectContext: TSomeObject

  constructor(objectContext: TSomeObject){
    this.objectContext = objectContext
  }

  setPropertiesToTrue(properties: Array<keyof { [K in keyof TSomeObject as TSomeObject[K] extends boolean ? K : never]: 0 }>){
    properties.forEach(property => {
      (this.objectContext as any)[property] = true
    })
  }
}


const someObj = new SomeObject(true, true, "hey");
const setter = new ProperySetter(someObj);
setter.setPropertiesToTrue(["x", "y"])

// @ts-expect-error not a boolean property
setter.setPropertiesToTrue(["z"]);

TypeScript Playground Link

  • Related