fix: plainToInstance from Decimal to Decimal throws an error (decimal.js)

Author: FilipSvozilCreated Aug 12, 2025Updated May 8, 2026
Labelstype: fixstatus: needs triage

Description

plainToInstance throws error on Decimal values, when they are already instanceof Decimal. This is relevant in situations, where the plainobject came from within the application and not over the network.

For plainobjects from JSON, this is not an issue, as Decimal values are always transported as a string.

Minimal code-snippet showcasing the problem

typescript
class FailingDto {
  @Transform(({ value }) => {
    console.log('transform', value);
    return new Decimal(value); // instanceToPlain direction omitted for brevity
  })
  public value!: Decimal;
}

plainToInstance(FailingDto, { value: 1 })
// console.log is printed
// returns instance with Decimal(1), as expected

plainToInstance(FailingDto, { value: new Decimal(1) })
// console.log is not printed
// throws "[DecimalError] Invalid argument: undefined"

Same issue happens also without any Transform, e.g. with following DTO:

typescript
class FailingDto {
  @Expose()
  public value!: Decimal;
}

Expected behavior

It should be possible to write a Transformer for Decimal values, without needing the String workaround below. (Or not having to write transformer at all would be best ofc.)

Note: new Decimal(new Decimal(1)) works just fine

Actual behavior

Throws [DecimalError] Invalid argument: undefined error

The error originates at newValue = new (targetType as any)(); in TransformOperationExecutor.ts:160 where class-transformer attempts to create a new instance without any constructor arguments. The Decimal constructor requires a value parameter and throws when called without arguments.

Current workarounds

Annotate the field with @Type(() => String) below the Transform.
But this is not ideal, as the field is not "String" it's "Decimal", so it's confusing for the reader.
Also it changes the value the @Transform will get, which is usually not relevant (Decimal values are usually serialized as string anyway), but it can become a footgun in some edge-cases (e.g. when I want to write transformer, that accepts Decimal | string | [number, number]).

typescript
class NoLongerFailingDto {
  @Transform(({ value }) => {
    console.log('transform', value);
    return new Decimal(value); // instanceToPlain direction omitted for brevity
  })
  @Type(() => String) // Must be String, not Number, to preserve decimal precision
  public value!: Decimal;
}

It looks more acceptable, when you hide the hack under a custom decorator, but still has the issue of unwanted string cast:

typescript
// nullable and optional variants omitted for brevity
function CustomDecorator(): PropertyDecorator {
  // instanceToPlain direction omitted for brevity
  const transformDecorator = Transform(({ value }) => new Decimal(value));
  const typeDecorator = Type(() => String);

  return (target, propertyKey) => {
    typeDecorator(target, propertyKey);
    transformDecorator(target, propertyKey);
  };
}

class NoLongerFailingDto {
  @CustomDecorator()
  public value!: Decimal;
}

Dependency versions

class-transformer: 0.5.1
typescript: 5.6.3
decimal.js: 10.4.3

Related issues

  • #1270
  • #1821
  • #1531
  • #1385
  • #1138

Source: typestack/class-transformer