Home > Blockchain >  Typescript generic function changing argument type
Typescript generic function changing argument type

Time:06-30

I have a question regarding generic function (specifically why i'm able to modify an object from specific type)

example :

interface UserConfig {
  name: string;
  age: number;
}

let user: UserConfig = {
    name: "Eyal",
    age: 23,
 };

function addTimestamp<T>(arg: T): T {
  return { ...arg, timestamp: new Date().toString() };
}

console.log(user); // {name: 'Eyal',age: 23}

user = addTimestamp<UserConfig>(user);

console.log(user); // { name: 'Eyal', age: 23, timestamp: 2022-06-29T16:28:31.524Z }

Why i'm able to mutate user variable when it should have UserConfig interface properties only (name and age)

As this won't work

...
user.timestamp = "xxxx" // ERROR - Property 'timestamp' does not exist on type 'UserConfig'

CodePudding user response:

First, you are not mutating "user", you are creating a new object that has all the fields of user, plus the timestamp field--the original user object doesn't change.

TypeScript doesn't complain here because the new object you return is still compatible with the original type T--you've not changed or removed any existing properties, you've just added new ones. It's structurally compatible with the original type.

interface UserConfig {
  name: string;
  age: number;
}

let user: UserConfig = {
  name: "Eyal",
  age: 23,
};

function addTimestamp<T>(arg: T): T {
  return { ...arg, timestamp: new Date().toString() };
}

let newUser: UserConfig = addTimestamp(user);

console.log(user === newUser); // false
console.log('timestamp' in user); // false
console.log('timestamp' in newUser); // true

let typeTest: UserConfig = (() => {
  return {
    name: 'Pizza',
    age: 49,
    isVeryCool: true
  }
})(); // compiles just fine

// @ts-expect-error
console.log(typeTest.isVeryCool); // doesn't compile if I remove the directive
console.log('isVeryCool' in typeTest); // true

You can argue that this is a weird thing to allow, especially given that TypeScript will block other kinds of related additions (for example, try removing the IIFE from my typeTest example and using the literal directly), but it's technically correct with the rest of the type system rules.

CodePudding user response:

You are simply over complicating the issue. Currently you take T and then return T so timestamp is never present as UserConfig has no property called timestamp. You can instead use extend which is the easier way of doing this:

interface UserConfig {
  name: string;
  age: number;
}

interface UserWithTime extends UserConfig {
  timestamp: string;
}

let user: UserConfig = {
  name: "Eyal",
  age: 23
};

function addTimestamp(arg: UserConfig): UserWithTime {
  return { ...arg, timestamp: new Date().toString() };
}

const userWithTime = addTimestamp(user);

console.log(userWithTime.timestamp);

If you absolutely must for some reason have a function that adds time without a secondary extends you can:

interface UserConfig {
  name: string;
  age: number;
}

let user: UserConfig = {
  name: "Eyal",
  age: 23
};

function addTimestamp<T>(arg: T): T & { timestamp: string } {
  return { ...arg, timestamp: new Date().toString() };
}

const userWithTime = addTimestamp(user);

console.log(userWithTime.timestamp);
  • Related