Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to type constructor argument to initialise properties from plain object

Tags:

typescript

I'm trying to come up with a simple way to write a class with a constructor that takes a plain object argument, and initialises the instance properties accordingly.

class Foo {
  x: string
  y: string

  constructor(init: Foo) {
    Object.assign(this, init)
  }
}


new Foo({x: 'a', y: 'b'})

This gives an error on the two properties: "has no initializer and is not definitely assigned in the constructor". If init is a valid Foo, which the type system says it is, those properties are definitely assigned in the constructor. I realise that assertion relies on an understanding of Object.assign, but I've seen other examples where it seems the compiler does have that.

What would be the best way to fix this? Right now I'm adding initialisers, but I'd prefer not to.

like image 421
Tom Locke Avatar asked Dec 26 '21 21:12

Tom Locke


People also ask

What is the need for initialization of object using constructor?

A class object with a constructor must be explicitly initialized or have a default constructor. Except for aggregate initialization, explicit initialization using a constructor is the only way to initialize non-static constant and reference class members.

How do you declare and initialize an object in TypeScript?

To initialize an object in TypeScript, we can create an object that matches the properties and types specified in the interface. export interface Category { name: string; description: string; } const category: Category = { name: "My Category", description: "My Description", };

How do I create a class object in TypeScript?

Creating an Object of Class An object of the class can be created using the new keyword. class Employee { empCode: number; empName: string; } let emp = new Employee(); Here, we create an object called emp of type Employee using let emp = new Employee(); .


1 Answers

The compiler cannot perform the analysis on Object.assign(this, init) to know that all the properties of this will be initialized as a result. The typings for Object.assign(target, ...args) don't mutate the type of the target parameter at all.

You could use a definite assignment assertion for each and every property, to suppress the error:

class Foo {
  x!: string
  y!: string

  constructor(init: Foo) {
    Object.assign(this, init)
  }
}

and that's fine for a single Foo class with only two properties. But it could be quite tedious indeed if you have lots of properties to initialize.


In such cases, you could write a class factory function which generalizes the pattern of copying the constructor parameter into this. You only have to do a single type assertion inside the implementation:

function ClassFor<T extends object>() {
  return class {
    constructor(init: any) {
      Object.assign(this, init);
    }
  } as new (init: T) => T;
}

And then you can just use the factory to generate your specific class constructors:

class Foo extends ClassFor<{ x: string, y: string }>() {

}

And verify that it behaves as you like:

const foo = new Foo({ x: 'a', y: 'b' })    
console.log(foo.x.toUpperCase()) // "A"
console.log(foo.y.toUpperCase()) // "B"

Playground link to code

like image 85
jcalz Avatar answered Oct 13 '22 19:10

jcalz