typestack / class-transformer

Decorator-based transformation, serialization, and deserialization between objects and classes.
MIT License
6.78k stars 495 forks source link

fix: Unexpected behavior when tsconfig "target" is set to "ES2022" or "ESNext" #1216

Open lockHrt opened 2 years ago

lockHrt commented 2 years ago

Description

If tsconfig.json has "target": "ES2022" or "target": "ESNext", the results are different from what have been mentioned in the documentation.

Minimal code-snippet showcasing the problem

This very simple example is taken from the documentation itself.

import { plainToClass } from 'class-transformer';

class User {
  id: number;
  firstName: string;
  lastName: string;
}

const fromPlainUser = {
  unkownProp: 'hello there',
  firstName: 'Umed',
  lastName: 'Khudoiberdiev',
};

console.log(plainToClass(User, fromPlainUser));

// User {
//   unkownProp: 'hello there',
//   firstName: 'Umed',
//   lastName: 'Khudoiberdiev',
// }

Expected console log

When tsconfig target is 2021 or lower

User {
  unkownProp: 'hello there',
  firstName: 'Umed',
  lastName: 'Khudoiberdiev'
}

Actual console log

When tsconfig target is 2022 or higher

User {
  id: undefined,
  firstName: 'Umed',
  lastName: 'Khudoiberdiev',
  unkownProp: 'hello there'
}

Other files

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
  }
}

package.json

...
"dependencies": {
    "class-transformer": "^0.5.1",
    "reflect-metadata": "^0.1.13",
    "typescript": "^4.7.2"
}
...

node version is 16.14.2

hasezoey commented 2 years ago

the difference is probably because of public instance class fields

target: es2021 and below would output:

class User {}

target: es2022 and above would output:

class User {
  id;
  firstName;
  lastName;
}

and this means that when you would do new User(), then the following would happen:

this means that js works by adding all defined values, even if not set by the constructor Also see MDN: Classes: Field declarations Quote:

By declaring fields up-front, class definitions become more self-documenting, and the fields are always present.

PS: the documentation was made before es2022 was available, and it does not seem like the documentation (or package itself) will be updated any time soon)

sw1tchdev commented 1 year ago

Be careful, if you use target: 'es2022' and the exposeUnsetFields: false option, unset fields will be exposed. Affected lines of code: https://github.com/typestack/class-transformer/blob/master/src/TransformOperationExecutor.ts#L160

MickL commented 1 year ago

I can confirm the behavior is different with newer target versions:

class Cat {
    name: string;
    age?: number;
}

const plainObj: any = {
    name: 'Meowth',
};
const cat = plainToInstance<Cat, any>(Cat, plainObj);
MickL commented 1 year ago

@lockHrt In the title of this issue you probably meant "ES2022" instead of "ES2021"

lockHrt commented 1 year ago

@lockHrt In the title of this issue you probably meant "ES2022" instead of "ES2021"

You're right. Corrected the title.

erodriguezvesti commented 1 year ago

Same problem here using ES2022