【ITニュース解説】Why Your `@Transform` Decorator Doesn’t Run in class-transformer (and How to Fix It)
2025年09月07日に「Dev.to」が公開したITニュース「Why Your `@Transform` Decorator Doesn’t Run in class-transformer (and How to Fix It)」について初心者にもわかりやすく解説しています。
ITニュース概要
`class-transformer`で`@Transform`が動かないのは、デフォルトで入力オブジェクトに存在しないプロパティは変換されないため。`@Expose`を使うと、プロパティが存在しなくても変換が実行される。グローバル設定で`excludeExtraneousValues`や`strategy: 'exposeAll'`も選択可能。派生プロパティにはgetterを使う方法もある。
ITニュース解説
class-transformer ライブラリを利用している際に、@Transform デコレータが期待通りに動作しない問題と、その解決策について解説する。
class-transformer は、プレーンな JavaScript オブジェクト(例えば、API からのレスポンスデータ)を、定義したクラスのインスタンスに変換する際に便利なライブラリだ。この変換処理の中で、特定のプロパティに対して何らかの処理を加えたい場合に @Transform デコレータを使用する。しかし、初心者にとって予期せぬ挙動として、@Transform が適用されないケースが存在する。
具体例として、MobileSignupBody クラスを考えてみよう。このクラスは、ユーザー登録に必要なメールアドレスを受け取るためのデータ構造を定義している。
1import 'reflect-metadata'; 2import { plainToInstance, Transform } from 'class-transformer'; 3import { IsEmail, IsNotEmpty } from 'class-validator'; 4 5class MobileSignupBody { 6 @IsNotEmpty() 7 @IsEmail() 8 email!: string; 9 10 @Transform(({ obj }) => obj.email) 11 normalized_email?: string; 12} 13 14const dto = plainToInstance(MobileSignupBody, { email: 'abc@test.com' }); 15console.log(dto); 16console.log(dto.normalized_email);
このコードでは、email プロパティを変換して normalized_email プロパティに格納しようとしている。しかし、実行結果を見ると、normalized_email は undefined となっている。これはなぜだろうか。
class-transformer のデフォルトの動作では、変換元のプレーンなオブジェクトに存在しないプロパティに対しては、@Transform デコレータが実行されない。上記の例では、変換元のオブジェクト { email: 'abc@test.com' } に normalized_email プロパティが存在しないため、@Transform がスキップされてしまう。
この問題を解決するためには、@Expose デコレータを使用する。@Expose は、class-transformer に対して、「たとえプレーンなオブジェクトに存在しなくても、このプロパティを変換処理の対象に含める」という指示を与える。
1import { Expose, Transform } from 'class-transformer'; 2 3class MobileSignupBody { 4 @Expose() 5 @IsNotEmpty() 6 @IsEmail() 7 email!: string; 8 9 @Expose() 10 @Transform(({ obj }) => obj.email) 11 normalized_email?: string; 12} 13 14const dto = plainToInstance(MobileSignupBody, { email: 'abc@test.com' }); 15console.log(dto.normalized_email);
上記のように、normalized_email プロパティに @Expose を追加することで、@Transform が正常に実行され、normalized_email に email の値が格納されるようになる。emailプロパティに@Exposeを付与することも忘れないようにしよう。
@Expose をすべてのプロパティに付与するのが煩雑な場合は、グローバルな設定オプションを利用することもできる。
-
excludeExtraneousValues: このオプションをtrueに設定すると、@Exposeが付与されたプロパティのみが変換後のオブジェクトに残る。つまり、明示的に公開したいプロパティのみを指定する場合に有効だ。1const dto = plainToInstance(MobileSignupBody, { email: 'abc@test.com' }, { 2 excludeExtraneousValues: true, 3}); -
strategy: 'exposeAll':@Exposeデコレータにstrategy: 'exposeAll'を設定すると、クラス内のすべてのプロパティがデフォルトで公開される。@Transformを適用したいプロパティに@Exposeを付与するだけでよくなる。1import { Expose, Transform } from 'class-transformer'; 2 3@Expose({ strategy: 'exposeAll' }) 4class MobileSignupBody { 5 email!: string; 6 7 @Expose() 8 @Transform(({ obj }) => obj.email) 9 normalized_email?: string; 10}
また、別の解決策として、getter を利用する方法もある。getter は、プロパティの値がアクセスされるたびに計算されるため、@Expose や @Transform を使用する必要がない。
1class MobileSignupBody { 2 email!: string; 3 4 get normalized_email(): string | undefined { 5 return this.email; 6 } 7}
この方法では、normalized_email は email プロパティの値に基づいて動的に計算される。
まとめると、class-transformer で @Transform が期待通りに動作しない場合は、以下の点を確認する必要がある。
- 変換元のプレーンなオブジェクトに、変換先のプロパティが存在するか。
@Exposeデコレータを使用して、プロパティを変換処理の対象に含めるように指示しているか。- グローバルな設定オプション (
excludeExtraneousValuesやstrategy: 'exposeAll') を利用して、より簡潔に記述できるか。 - 単純な変換であれば、getter を利用することも検討する。
これらの点を理解することで、class-transformer をより効果的に活用し、データ変換処理をスムーズに進めることができるようになる。