class-transformer icon indicating copy to clipboard operation
class-transformer copied to clipboard

fix: plainToInstance will throw an error when exposing a getter in the parent class

Open CodyTseng opened this issue 2 years ago • 4 comments

Description

Minimal code-snippet showcasing the problem

import { Expose, plainToInstance } from 'class-transformer'

class Person {
  firstName: string;
  lastName: string;

  @Expose()
  get name() {
    return this.firstName + ' ' + this.lastName;
  }

  say() {
    console.log(`My name is ${this.name}`);
  }
}

class Programmer extends Person {
  language: string;

  say() {
    console.log(`My name is ${this.name}. I'm a ${this.language} programmer.`);
  }
}

const programmer = plainToInstance(Programmer, {
  firstName: 'Umed',
  lastName: 'Khudoiberdiev',
  language: 'JavaScript',
});

programmer.say();

console.log(instanceToPlain(programmer));

Expected behavior

Output:

My name is Umed Khudoiberdiev. I'm a JavaScript programmer.
{
  firstName: 'Umed',
  lastName: 'Khudoiberdiev',
  language: 'JavaScript',
  name: 'Umed Khudoiberdiev'
}

Actual behavior

/****/node_modules/class-transformer/cjs/TransformOperationExecutor.js:309
                            newValue[newValueKey] = finalValue;
                                                  ^

TypeError: Cannot set property name of #<Person> which has only a getter
    at TransformOperationExecutor.transform (/****/node_modules/class-transformer/cjs/TransformOperationExecutor.js:309:51)
    at ClassTransformer.plainToInstance (/****/node_modules/class-transformer/cjs/ClassTransformer.js:27:25)
    at plainToInstance (/****/node_modules/class-transformer/cjs/index.js:38:29)
    at Object.<anonymous> (/****/index.js:31:60)
    at Module._compile (node:internal/modules/cjs/loader:1103:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1157:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
    at node:internal/main/run_main_module:17:47

CodyTseng avatar Jul 01 '22 08:07 CodyTseng

same problem

hcg1023 avatar Aug 28 '22 03:08 hcg1023

same issue here

I saw the PR has been raised up, any idea when this can be merged?

zh3ngyuan avatar Oct 13 '22 19:10 zh3ngyuan

Damn I just discovered the same!

As workaround do I have to duplicate the getter on the extending class? Although I'm almost sure this used to work in NestJS...probably I'm using their version of the class-transformer... I'll update here...

For now I can Confirm that duplicating the getter with the decorators solved

tonysamperi avatar Jul 09 '23 12:07 tonysamperi

In our application the Expose decorator wasn't working for getters. Nothing gets passed along. So we copied the getters into the DTO as properties to avoid the getter issue, added the Transform decorator to each property that needed it, and applied the OmitType to the extended entity which avoids the error you get for overriding a getter with a property. It isn't idle since you're overcoming strict type checks, but it's only to create a DTO which wouldn't have a getter in most cases (ie. passed to client), and you still have the inheriting DTO getting all the other entity properties applied since it will match it structurally:

export class ExampleDto extends OmitType(ExampleEntity, [] as const) {
  // ... removed for brevity

  @Transform(({ obj }) => obj.whatever !== ‘something’, { toClassOnly: true })
  public exampleField: boolean; // <--- is a getter in ExampleEntity that matches the Transform
}

mtpultz1977 avatar Apr 03 '24 15:04 mtpultz1977