class-transformer
class-transformer copied to clipboard
fix: instanceToInstance fails for discriminator with null value - Cannot read properties of null (reading 'constructor')
Description
When using instanceToInstance on an object that has a discriminated property with a null value, and exception gets thrown. Note that plainToInstance works fine
Minimal code-snippet showcasing the problem
Here is a repository with a test showing the issue.
import { Exclude, Expose, instanceToInstance, plainToInstance, Type } from "class-transformer";
export class FullName {
public first?:string;
public last?:string;
}
export class LockedValue {
private readonly value:string;
private readonly __type = "LockedValue";
constructor(value:string) {
this.value = value;
}
public toString():string {
return this.value;
}
}
@Exclude()
export class User {
// My use case is I want to default to FullName object if no discriminator is present. Otherwise, I want to create an instance of LockedValue
@Type(() => FullName, {
keepDiscriminatorProperty: true,
discriminator: {
property: "__type",
subTypes: [
{ value: LockedValue, name: "LockedValue" },
]
}
})
@Expose()
public fullName?:FullName | LockedValue;
}
describe('class-transformer @Type discriminator', () => {
it('should create a copy using plainToInstance when discrimnated field is null', async () => {
let u = new User();
u.fullName = null;
// works fine
let copy = plainToInstance(User, u);
expect(copy == u).toBeFalse();
})
it('should create a copy using instanceToInstance when discrimnated field is null', async () => {
let u = new User();
u.fullName = null;
// throws exception Cannot read properties of null (reading 'constructor')
let copy = instanceToInstance(u);
expect(copy == u).toBeFalse();
})
})
Expected behavior
expect no exception
Actual behavior
Error details
Message:
TypeError: Cannot read properties of null (reading 'constructor')
Stack:
at TransformOperationExecutor.transform (C:\Users\joelw\eclipse-workspace\tower-poi\packages\bug-reports\node_modules\src\TransformOperationExecutor.ts:244:35)
at ClassTransformer.instanceToInstance (C:\Users\joelw\eclipse-workspace\tower-poi\packages\bug-reports\node_modules\src\ClassTransformer.ts:113:21)
at Object.instanceToInstance (C:\Users\joelw\eclipse-workspace\tower-poi\packages\bug-reports\node_modules\src\index.ts:106:27)
at UserContext.<anonymous> (C:\Users\joelw\eclipse-workspace\tower-poi\packages\bug-reports\projects\class-transformer-issues\src\tst\discriminator.spec.ts:51:20)
at <Jasmine>
As a side note, my use case involves union discrimination rather than inheritance. I don't think it really changes anything with regards to this issue but worth mention. It is a security feature used for wrapping data that a client is not allowed to view
I just hit same issue
Here is my example
import "reflect-metadata";
import {
Expose,
instanceToInstance,
Type
} from "class-transformer";
import { IsString } from "class-validator";
class SubDto {
@Expose()
@IsString()
str: string;
}
class SubExtendedADto extends SubDto {
@Expose()
@IsString()
extendedA: string;
}
class SubExtendedBDto extends SubDto {
@Expose()
@IsString()
extendedB: string;
}
class MainDto {
@Expose()
@Type(() => SubDto, {
discriminator: {
property: 'str',
subTypes: [
{ value: SubExtendedADto, name: 'A' },
{ value: SubExtendedBDto, name: 'B' }
],
},
keepDiscriminatorProperty: true,
})
sub: SubDto | null = null;
}
const dto = new MainDto();
const obj = instanceToInstance(dto);