Skip to content

fix: plainToInstance throws on inherited getter-only properties when either the plain object contains the key or the getter is exposed #1823

@Woodz

Description

@Woodz

Description

Describe the bug
When using plainToInstance from class-transformer, a runtime error is thrown if a class inherits a getter-only property and either:

The plain object contains a property with the same name, or

The inherited getter is decorated with @expose() — even if the plain object is empty.

In contrast, non-inherited getter-only properties are silently ignored under the same conditions, leading to inconsistent and surprising behavior.

Minimal code-snippet showcasing the problem

import "reflect-metadata";
import { plainToInstance, Expose } from "class-transformer";

abstract class Base {
  get inheritedOnlyGetter(): string {
    return "base";
  }
}

class Example extends Base {
  get localOnlyGetter(): string {
    return "local";
  }
}

const plainWithLocalGetter = {
  localOnlyGetter: "value",
};
const plainWithInheritedGetter = {
  inheritedOnlyGetter: "value",
};

const instanceWithLocalGetter = plainToInstance(Example, plainWithLocalGetter);
console.log(
  `Plain with local getter converted successfully: ${JSON.stringify(
    instanceWithLocalGetter
  )}`
);

try {
  plainToInstance(Example, plainWithInheritedGetter);
} catch (err) {
  console.log(`Plain with inherited getter failed conversion: ${err}`);
}

class ClassWithOwnExposedGetter {
  @Expose()
  get getter(): string {
    return "own";
  }
}

class ClassWithInheritedExposedGetter extends ClassWithOwnExposedGetter {}

const instanceWithOwnExposedGetter = plainToInstance(
  ClassWithOwnExposedGetter,
  {}
);
console.log(
  `Plain with own exposed getter converted successfully: ${JSON.stringify(
    instanceWithOwnExposedGetter
  )}`
);

try {
  plainToInstance(ClassWithInheritedExposedGetter, {});
} catch (err) {
  console.log(`Plain with exposed inherited getter failed conversion: ${err}`);
}

Expected behavior

Consistency between non-inherited and inherited getters

Actual behavior

Non-inherited getters never throw. Inherited getters throw if exposed or plain object has a value for it

Metadata

Metadata

Assignees

No one assigned

    Labels

    status: needs triageIssues which needs to be reproduced to be verified report.type: fixIssues describing a broken feature.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions