class-transformer
class-transformer copied to clipboard
fix: Exposing properties with different names does not work with nested objects.
Description
I'm trying to expose a custom property on a nested object, but it returns undefined instead of calling the getter.
Minimal code-snippet showcasing the problem
@Exclude()
class SubEntity {
@Expose()
@Transform(({ obj }) => obj.name.toUpperCase())
working: string;
@Expose()
name: string;
@Expose({ name: "broken" })
get entity() {
return this.name.toUpperCase();
}
}
@Exclude()
export class Test {
@Expose({ name: "name" })
getName() {
return "This gets exposed";
}
@Expose()
@Type(() => SubEntity)
entity: SubEntity;
constructor() {
Object.assign(this, { entity: { name: "name" } });
}
}
new ClassTransformer().classToPlain(new Test());
Expected behavior
I expect the following output:
{
name: 'This gets exposed',
entity: { working: 'NAME', name: 'name', broken: 'NAME' }
}
Actual behavior
This is what I get:
{
name: 'This gets exposed',
entity: { working: 'NAME', name: 'name', broken: undefined }
}
This is a bug and forces me to use @Transform instead of @Expose as desired.
I have some problem with you. Please show me @Transform decorator usage for custom different property name.
Confirmed, it's broken
Not only nested object, but even direct objects exposed under a different name are undefined.
I wanted to just directly expose id
as _id
for mongoose/mongodb sake.
This is the workaround I came up with, downside being that both properties are now present with the same value on the object when only one is needed.
export class FreelancerSortInput extends TimeStampsSortInput {
@IsEnum(Sort)
@IsOptional()
@Transform(o => o.obj.id)
@Expose()
_id?: Sort
@Field(() => Sort, {
nullable: true,
description: 'Sort by ID either ascending (ASC) or descrending (DESC)',
})
@IsEnum(Sort)
@IsOptional()
id?: Sort
}
Any update on this issue ?
I have the same issue when exposing nested objects in an array.
Facing the exact same issue, is it still borken?
Same issue. The only workaround is to use transform instead of @Type
which is not ideal.
As @calebpitan mentioned, even direct objects exposed under a different name are undefined too. Using the example in the README, id
will be undefined if uid
isn't a property in the source object:
export class User {
@Expose({ name: 'uid' })
id: number;
}
However, this will work using Transform with id
taking on uid
's value:
export class User {
@Transform(({ obj, value }) => value ? value : obj.uid))
id: number;
}
And if uid
isn't a property on the source object, id
will still have a value.
Broken confirmed.
I use the following to transform a JWT with its shorthand properties to more human-readable properties. These shorthand properties should be transformed into the 3-character properties when transforming to plain, so serialization will mirror the original input.
This works fine when MemberJwt is transformed by itself, but when nested inside the Session class, things fall apart.
import 'reflect-metadata'
import { Expose, instanceToPlain, plainToInstance, Type } from 'class-transformer'
class MemberJwt {
@Expose({ name: 'iss' })
issuser: string
@Expose({ name: 'sub' })
subject: string
}
class Session {
id: string
@Type(() => MemberJwt)
member: MemberJwt
}
const member = plainToInstance(MemberJwt, { iss: 'test', sub: '123' })
const memberPlain = instanceToPlain(member)
const session = plainToInstance(Session, { id: '333', member })
it('should transform member JWT to instance using @Expose aliases', () => {
expect(member.issuser).toBe('test')
expect(member.subject).toBe('123')
})
it('should transform member JWT to shorthand props', () => {
expect(memberPlain.iss).toBe('test')
expect(memberPlain.sub).toBe('123')
})
it('should retain member JWT instance properties within transformed Session instance', () => {
expect(session.id).toBe('333')
expect(session.member).toBeDefined()
expect(session.member.issuser).toBe('test')
expect(session.member.subject).toBe('123')
})
it('should not retain @Expose alias properties within transformed Session instance', () => {
expect(session.member['iss']).toBeUndefined()
expect(session.member['sub']).toBeUndefined()
})
The fix I found is to not use @Expose at all, and use getters:
class MemberJwt {
private iss: string
private sub: string
get issuser(): string {
return this.iss
}
get subject(): string {
return this.sub
}
}
Had the same issue, using getters worked for me. Thanks @nolawnchairs
This works for me when I invert the name of the property with the options name for the Expose decorator:
@Expose({ name: 'originalPropertyName'})
newPropertyName: number;