I find a few questions already like this. But I don't know how to fixed more alike typscript style. I don't want to use 'as'.

These are my some questions example:

First Error: Type '{}' is not assignable to type 'T'.


function copy<T extends Record<string, any>>(target: T): T {
  const res: T = {} // Error 1: Type '{}' is not assignable to type 'T'.

  Object.keys(target).forEach(key => {
    if (typeof target[key] === 'object') res[key] = copy(target[key])
    else if (typeof target[key] === 'string') res[key] = target[key];
  });

  return res
}

Second Error: Type 'Record<string, any>' is not assignable to type 'T'


function copy<T extends Record<string, any>>(target: T): T {
  const res: Record<string, any> = {};

  Object.keys(target).forEach(key => {
    if (typeof target[key] === 'object') res[key] = copy(target[key])
    else if (typeof target[key] === 'string') res[key] = target[key];
  });

  return res // Type 'Record<string, any>' is not assignable to type 'T'.
}

And I use 'as' to fixed, see: ts playground


function copy<T extends Record<string, any>>(target: T): T {
  const res: Record<string, any> = {};

  Object.keys(target).forEach(key => {
    if (typeof target[key] === 'object') res[key] = copy(target[key])
    else if (typeof target[key] === 'string') res[key] = target[key];
  });

  return res as T;
}

Do somebody have other ways to solve this? Or how can I pass T type to function and want to return T Type, T need extends object or Record.

1

There are 1 best solutions below

1
heystewart On

These are both essentially the same problem. You know that T looks like a Record, but you don't know anything else about it, so initializing it with {} is not safe, and since TypeScript can't guarantee it, it doesn't let you do it.

It's like having a class Cat and trying to initialize it with an instance of a class Animal ... you know that it is an Animal, but that's not enough to properly initialize a Cat.

Specifically, for your first error you are trying to initialize a Cat with an instance of Animal, and for your second error, you have created an Animal and you're trying to return it as a Cat.

Here's an example with your copy that would fail if it was permitted.

let counter = 0;
class MyClass implements Record<string, any> {
  public instanceNumber: number;
  constructor() {
    console.log("Reporting to central command, new MyClass")
    this.instanceNumber = counter++;
  }

  printInstanceNumber() {
    console.log(`I am ${this.instanceNumber}`);
  }
}

function copy<T extends Record<string, any>>(target: T): T {
  const res: Record<string, any> = {};
  Object.keys(target).forEach(key => {
    if (typeof target[key] === 'object') res[key] = copy(target[key])
    else if (typeof target[key] === 'string') res[key] = target[key];
    else if (typeof target[key] === 'number') res[key] = target[key];
  });
  return res as T;
}

const originalInstance = new MyClass();

const myCopy = copy(originalInstance);

// Has the wrong instance number...
console.log("original instanceNumber:", originalInstance.instanceNumber); // 0
console.log("copied instanceNumber:", myCopy.instanceNumber); // 0

// And crashes, no printInstanceNumber
originalInstance.printInstanceNumber(); // works
myCopy.printInstanceNumber(); // crashes, myCopy.printInstanceNumber is not a function