typestack / class-transformer

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

fix: cannot read property 'name' of undefined #803

Open inmativ opened 3 years ago

inmativ commented 3 years ago

This is my first issue. Please be friendly. Pardon my bad english.

Description

I am writing a project in NestJS. An error occurs when validating an array with objects of different types. The error is reproduced only if you refer to the route using Postman. If you just call validate (new Facts (...)), everything works as expected. I tried on versions 0.3.1 and 0.4.0.

Discriminator parameters

  @ArrayNotEmpty()
  @ValidateNested()
  @Type(() => FactDTO, {
    discriminator: {
      property: 'eventTypeCode',
      subTypes: [
        { value: FlightExecutionFactDTO, name: '1' },
        { value: FlightFinishFactDTO, name: '2' },
      ],
    },
    keepDiscriminatorProperty: true,
  })
  facts: (FlightExecutionFactDTO | FlightFinishFactDTO)[];
}

Parent class decorators

export class FactDTO {
  @ApiProperty()
  @IsRFC3339()
  moment: string;

  @ApiProperty()
  @IsEnum(EventTypeCode)
  eventTypeCode: EventTypeCode;

  @IsNotEmptyObject()
  event: unknown;
}

FlightExecutionFactDTO and FlightFinishFactDTO extend FactDTO

Validation parameters

const validationPipe = new ValidationPipe({
  whitelist: true,
  forbidNonWhitelisted: true,
});
app.useGlobalPipes(validationPipe);

The error occurs when accessing the 'name' field, when the iteration does not find any matching element. The debugger shows that subType.value is one of the classes that extends FactDTO, and subValue.constructor is FactDTO itself. Of course, no coincidence occurs.

if (this.transformationType === TransformationType.CLASS_TO_PLAIN) {
  subValue[targetType.options.discriminator.property] = targetType.options.discriminator.subTypes.find(
  subType => subType.value === subValue.constructor
  ).name;
}

Expected behavior

The array of incoming objects is validated in accordance with the decorators of each object.

Actual behavior

An error is thrown when trying to match classes, as a result, the 'name' field cannot be read.

smaven commented 3 years ago

@inmativ can you try deleting the dist folder and then start nest again? Re-transpiling the files seems to resolve quite a few issues sometimes.

inmativ commented 2 years ago

The error was due to the type "name: string" in the properties of the discriminator. And these values could not communicate with "eventTypeCode: Enum". To solve this, us had to use tsIgnore, since you cannot specify "Enum" for the "name" property.

This is what the working code looks like:

@ArrayNotEmpty()
  @ValidateNested()
  @Type(() => NewFactDTO, {
    discriminator: {
      property: 'type',
      subTypes: [
        // @ts-ignore
        { value: NewFlightExecutionFactDTO, name: EventType.FLIGHT_EXECUTION },
        // @ts-ignore
        { value: NewFlightFinishFactDTO, name: EventType.FLIGHT_FINISH },
      ],
    },
    keepDiscriminatorProperty: true,
  })
  facts: (
    | NewFlightExecutionFactDTO
    | NewFlightFinishFactDTO
  )[];
Ash-Kay commented 2 years ago

I'm facing same issue, adding // @ts-ignore doesn't help, even tried using strings in name field, still facing the issue, I think it is not related to Enum

Error

TypeError: Cannot read properties of undefined (reading 'name') at TransformOperationExecutor.transform `(mypath/node_modules/src/TransformOperationExecutor.ts:250:22)

Code:


@ApiExtraModels(BlockDto, IconLinkSectionDto, TextBlockDto)
export class AddNewBlockDto {
    @ApiProperty()
    @IsString()
    blockType: string;

    @ApiProperty({
        type: "object",
        oneOf: [{ $ref: getSchemaPath(IconLinkSectionDto) }, { $ref: getSchemaPath(TextBlockDto) }],
    })
    @IsObject()
    @Type(() => BlockDto, {
        discriminator: {
            property: "blockType",
            subTypes: [
                { value: IconLinkSectionDto, name: "a" },
                { value: TextBlockDto, name: "b" },
            ],
        },
    })
    block: IconLinkSectionDto | TextBlockDto;
}
tobinbc commented 9 months ago

I'm facing same issue, adding // @ts-ignore doesn't help, even tried using strings in name field, still facing the issue, I think it is not related to Enum

Error

TypeError: Cannot read properties of undefined (reading 'name') at TransformOperationExecutor.transform `(mypath/node_modules/src/TransformOperationExecutor.ts:250:22)

Code:

@ApiExtraModels(BlockDto, IconLinkSectionDto, TextBlockDto)
export class AddNewBlockDto {
    @ApiProperty()
    @IsString()
    blockType: string;

    @ApiProperty({
        type: "object",
        oneOf: [{ $ref: getSchemaPath(IconLinkSectionDto) }, { $ref: getSchemaPath(TextBlockDto) }],
    })
    @IsObject()
    @Type(() => BlockDto, {
        discriminator: {
            property: "blockType",
            subTypes: [
                { value: IconLinkSectionDto, name: "a" },
                { value: TextBlockDto, name: "b" },
            ],
        },
    })
    block: IconLinkSectionDto | TextBlockDto;
}

For what it's worth, I had the same as you but this was caused by including ApiExtraModels on the field. When removed, the problem goes away.