ES6 と Typescript の時代。最近では、これまで以上にクラスやコンストラクター オブジェクトを扱うことが多くなってきています。クラストランスフォーマーを使用すると、プレーンオブジェクトをクラスのインスタンスに変換したり、その逆を行うことができます。また、基準に基づいてオブジェクトをシリアル化/逆シリアル化することもできます。このツールはフロントエンドとバックエンドの両方で非常に便利です。
prunker で angular 2 を使用する例。ソースコードはここから入手できます。
JavaScript には 2 種類のオブジェクトがあります。
プレーン オブジェクトは、 Object
クラスのインスタンスであるオブジェクトです。 {}
表記法を使用して作成された場合、それらはリテラルオブジェクトと呼ばれることもあります。クラス オブジェクトは、独自に定義されたコンストラクター、プロパティ、メソッドを持つクラスのインスタンスです。通常、 class
表記を使用してそれらを定義します。
それで、何が問題なのでしょうか?
プレーンな JavaScript オブジェクトを ES6クラスに変換したい場合があります。たとえば、バックエンド、API、または JSON ファイルから JSON をロードし、それをJSON.parse
すると、クラスのインスタンスではなく、プレーンな JavaScript オブジェクトが得られます。
たとえば、 users.json
にユーザーのリストをロードしているとします。
[
{
"id" : 1 ,
"firstName" : " Johny " ,
"lastName" : " Cage " ,
"age" : 27
},
{
"id" : 2 ,
"firstName" : " Ismoil " ,
"lastName" : " Somoni " ,
"age" : 50
},
{
"id" : 3 ,
"firstName" : " Luke " ,
"lastName" : " Dacascos " ,
"age" : 12
}
]
そして、 User
クラスがあります。
export class User {
id : number ;
firstName : string ;
lastName : string ;
age : number ;
getName ( ) {
return this . firstName + ' ' + this . lastName ;
}
isAdult ( ) {
return this . age > 36 && this . age < 60 ;
}
}
ここでは、 users.json
ファイルからUser
タイプのユーザーをダウンロードしていると想定しており、次のコードを記述するとよいでしょう。
fetch ( 'users.json' ) . then ( ( users : User [ ] ) => {
// you can use users here, and type hinting also will be available to you,
// but users are not actually instances of User class
// this means that you can't use methods of User class
} ) ;
このコードでは、 users[0].id
を使用できます。また、 users[0].firstName
およびusers[0].lastName
も使用できます。ただし、「users」は実際には User オブジェクトのインスタンスではなく、プレーンな JavaScript オブジェクトの配列であるため、 users[0].getName()
またはusers[0].isAdult()
は使用できません。 users: User[]
であると言ったとき、実際にはコンパイラーに嘘をついていました。
それで、どうすればいいでしょうか?プレーンな JavaScript オブジェクトの代わりにUser
オブジェクトのインスタンスのusers
配列を作成するにはどうすればよいですか?解決策は、User オブジェクトの新しいインスタンスを作成し、すべてのプロパティを新しいオブジェクトに手動でコピーすることです。ただし、オブジェクト階層がより複雑になると、すぐに問題が発生する可能性があります。
代替案?はい、クラストランスフォーマーを使用できます。このライブラリの目的は、プレーンな JavaScript オブジェクトをクラスのインスタンスにマップできるようにすることです。
このライブラリは、モデルが API で公開するものを制御するための優れたツールを提供するため、API で公開されるモデルにも最適です。以下にその例を示します。
fetch ( 'users.json' ) . then ( ( users : Object [ ] ) => {
const realUsers = plainToInstance ( User , users ) ;
// now each user in realUsers is an instance of User class
} ) ;
これで、 users[0].getName()
とusers[0].isAdult()
メソッドを使用できるようになりました。
モジュールをインストールします:
npm install class-transformer --save
reflect-metadata
shim が必要なので、それもインストールします。
npm install reflect-metadata --save
そして、それを app.ts のようなグローバルな場所にインポートしてください。
import 'reflect-metadata' ;
ES6 の機能が使用されます。古いバージョンの Node.js を使用している場合は、es6-shim をインストールする必要がある場合があります。
npm install es6-shim --save
そしてそれを app.ts のようなグローバルな場所にインポートします。
import 'es6-shim' ;
モジュールをインストールします:
npm install class-transformer --save
reflect-metadata
shim が必要なので、それもインストールします。
npm install reflect-metadata --save
<script>
index.html
の先頭のreflect-metadataに追加します。
< html >
< head >
<!-- ... -->
< script src =" node_modules/reflect-metadata/Reflect.js " > </ script >
</ head >
<!-- ... -->
</ html >
angular 2 を使用している場合は、このシムがすでにインストールされているはずです。
system.js を使用している場合は、これをmap
とpackage
構成に追加するとよいでしょう。
{
"map" : {
"class-transformer" : " node_modules/class-transformer "
},
"packages" : {
"class-transformer" : { "main" : " index.js " , "defaultExtension" : " js " }
}
}
このメソッドは、プレーンな JavaScript オブジェクトを特定のクラスのインスタンスに変換します。
import { plainToInstance } from 'class-transformer' ;
let users = plainToInstance ( User , userJson ) ; // to convert user plain object a single user. also supports arrays
このメソッドは、ターゲット クラスのインスタンスである既に入力されたオブジェクトを使用して、プレーン オブジェクトをインスタンスに変換します。
const defaultUser = new User ( ) ;
defaultUser . role = 'user' ;
let mixedUser = plainToClassFromExist ( defaultUser , user ) ; // mixed user should have the value role = user when no value is set otherwise.
このメソッドは、クラス オブジェクトをプレーンな JavaScript オブジェクトに戻します。これは、後でJSON.stringify
になる可能性があります。
import { instanceToPlain } from 'class-transformer' ;
let photo = instanceToPlain ( photo ) ;
このメソッドは、クラス オブジェクトをクラス オブジェクトの新しいインスタンスに変換します。これは、オブジェクトのディープ クローンとして扱われる場合があります。
import { instanceToInstance } from 'class-transformer' ;
let photo = instanceToInstance ( photo ) ;
変換オプションでignoreDecorators
オプションを使用して、クラスが使用しているすべてのデコレーターを無視することもできます。
serialize
メソッドを使用して、モデルを直接 JSON にシリアル化できます。
import { serialize } from 'class-transformer' ;
let photo = serialize ( photo ) ;
serialize
配列と非配列の両方で動作します。
deserialize
メソッドを使用して、json からモデルを逆シリアル化できます。
import { deserialize } from 'class-transformer' ;
let photo = deserialize ( Photo , photo ) ;
逆シリアル化を配列で機能させるには、 deserializeArray
メソッドを使用します。
import { deserializeArray } from 'class-transformer' ;
let photos = deserializeArray ( Photo , photos ) ;
plainToInstance
メソッドのデフォルトの動作では、クラスで指定されていないプロパティも含め、プレーン オブジェクトからすべてのプロパティが設定されます。
import { plainToInstance } from 'class-transformer' ;
class User {
id : number ;
firstName : string ;
lastName : string ;
}
const fromPlainUser = {
unkownProp : 'hello there' ,
firstName : 'Umed' ,
lastName : 'Khudoiberdiev' ,
} ;
console . log ( plainToInstance ( User , fromPlainUser ) ) ;
// User {
// unkownProp: 'hello there',
// firstName: 'Umed',
// lastName: 'Khudoiberdiev',
// }
この動作がニーズに合わない場合は、すべてのクラス プロパティを要件として公開しながら、 plainToInstance
メソッドでexcludeExtraneousValues
オプションを使用できます。
import { Expose , plainToInstance } from 'class-transformer' ;
class User {
@ Expose ( ) id : number ;
@ Expose ( ) firstName : string ;
@ Expose ( ) lastName : string ;
}
const fromPlainUser = {
unkownProp : 'hello there' ,
firstName : 'Umed' ,
lastName : 'Khudoiberdiev' ,
} ;
console . log ( plainToInstance ( User , fromPlainUser , { excludeExtraneousValues : true } ) ) ;
// User {
// id: undefined,
// firstName: 'Umed',
// lastName: 'Khudoiberdiev'
// }
ネストされたオブジェクトを含むオブジェクトを変換しようとする場合、変換しようとしているオブジェクトのタイプを知る必要があります。 Typescript にはまだ優れたリフレクション機能がないため、各プロパティに含まれるオブジェクトのタイプを暗黙的に指定する必要があります。これは@Type
デコレータを使用して行われます。
写真が入ったアルバムがあるとします。そして、アルバムのプレーンオブジェクトをクラスオブジェクトに変換しようとしています。
import { Type , plainToInstance } from 'class-transformer' ;
export class Album {
id : number ;
name : string ;
@ Type ( ( ) => Photo )
photos : Photo [ ] ;
}
export class Photo {
id : number ;
filename : string ;
}
let album = plainToInstance ( Album , albumJson ) ;
// now album is Album object with Photo objects inside
ネストされたオブジェクトが異なるタイプである可能性がある場合は、識別子を指定する追加のオプション オブジェクトを提供できます。 discriminator オプションは、オブジェクトのサブタイプ名と、ネストされたオブジェクトが変換できるsubTypes
を保持するproperty
を定義する必要があります。サブタイプには、 Type のコンストラクターを保持するvalue
と、識別子のproperty
と一致するname
があります。
トップ写真のあるアルバムがあるとします。しかし、この写真は特定の異なるタイプである可能性があります。そしてアルバムプレーンオブジェクトをクラスオブジェクトに変換しようとしています。プレーン オブジェクト入力では、追加のプロパティ__type
を定義する必要があります。このプロパティは、デフォルトでは変換中に削除されます。
JSON入力:
{
"id" : 1 ,
"name" : " foo " ,
"topPhoto" : {
"id" : 9 ,
"filename" : " cool_wale.jpg " ,
"depth" : 1245 ,
"__type" : " underwater "
}
}
import { Type , plainToInstance } from 'class-transformer' ;
export abstract class Photo {
id : number ;
filename : string ;
}
export class Landscape extends Photo {
panorama : boolean ;
}
export class Portrait extends Photo {
person : Person ;
}
export class UnderWater extends Photo {
depth : number ;
}
export class Album {
id : number ;
name : string ;
@ Type ( ( ) => Photo , {
discriminator : {
property : '__type' ,
subTypes : [
{ value : Landscape , name : 'landscape' } ,
{ value : Portrait , name : 'portrait' } ,
{ value : UnderWater , name : 'underwater' } ,
] ,
} ,
} )
topPhoto : Landscape | Portrait | UnderWater ;
}
let album = plainToInstance ( Album , albumJson ) ;
// now album is Album object with a UnderWater object without `__type` property.
ヒント: 異なるサブタイプを持つ配列にも同じことが当てはまります。さらに、オプションでkeepDiscriminatorProperty: true
を指定すると、結果のクラス内にも discriminator プロパティを保持できます。
ゲッターまたはメソッドに@Expose()
デコレーターを設定することで、ゲッターまたはメソッドが返すものを公開できます。
import { Expose } from 'class-transformer' ;
export class User {
id : number ;
firstName : string ;
lastName : string ;
password : string ;
@ Expose ( )
get name ( ) {
return this . firstName + ' ' + this . lastName ;
}
@ Expose ( )
getFullName ( ) {
return this . firstName + ' ' + this . lastName ;
}
}
一部のプロパティを別の名前で公開したい場合は、 @Expose
デコレータにname
オプションを指定することで実行できます。
import { Expose } from 'class-transformer' ;
export class User {
@ Expose ( { name : 'uid' } )
id : number ;
firstName : string ;
lastName : string ;
@ Expose ( { name : 'secretKey' } )
password : string ;
@ Expose ( { name : 'fullName' } )
getFullName ( ) {
return this . firstName + ' ' + this . lastName ;
}
}
変換中に一部のプロパティをスキップしたい場合があります。これは@Exclude
デコレータを使用して実行できます。
import { Exclude } from 'class-transformer' ;
export class User {
id : number ;
email : string ;
@ Exclude ( )
password : string ;
}
ユーザーを変換すると、 password
プロパティはスキップされ、変換結果には含まれなくなります。
どの操作でプロパティを除外するかを制御できます。 toClassOnly
またはtoPlainOnly
オプションを使用します。
import { Exclude } from 'class-transformer' ;
export class User {
id : number ;
email : string ;
@ Exclude ( { toPlainOnly : true } )
password : string ;
}
今後、 password
プロパティは、 instanceToPlain
操作中にのみ除外されます。逆に、 toClassOnly
オプションを使用します。
クラスのすべてのプロパティをスキップし、明示的に必要なプロパティのみを公開できます。
import { Exclude , Expose } from 'class-transformer' ;
@ Exclude ( )
export class User {
@ Expose ( )
id : number ;
@ Expose ( )
email : string ;
password : string ;
}
これで、 id
とemail
が公開され、パスワードは変換中に除外されます。あるいは、変換中に除外戦略を設定することもできます。
import { instanceToPlain } from 'class-transformer' ;
let photo = instanceToPlain ( photo , { strategy : 'excludeAll' } ) ;
この場合、クラス全体を@Exclude()
必要はありません。
プライベート プロパティにプレフィックス (たとえば_
を付けて名前を付けると、そのようなプロパティを変換から除外することもできます。
import { instanceToPlain } from 'class-transformer' ;
let photo = instanceToPlain ( photo , { excludePrefixes : [ '_' ] } ) ;
これにより、 _
プレフィックスで始まるすべてのプロパティがスキップされます。任意の数のプレフィックスを渡すことができ、これらのプレフィックスで始まるすべてのプロパティは無視されます。例えば:
import { Expose , instanceToPlain } from 'class-transformer' ;
export class User {
id : number ;
private _firstName : string ;
private _lastName : string ;
_password : string ;
setName ( firstName : string , lastName : string ) {
this . _firstName = firstName ;
this . _lastName = lastName ;
}
@ Expose ( )
get name ( ) {
return this . _firstName + ' ' + this . _lastName ;
}
}
const user = new User ( ) ;
user . id = 1 ;
user . setName ( 'Johny' , 'Cage' ) ;
user . _password = '123' ;
const plainUser = instanceToPlain ( user , { excludePrefixes : [ '_' ] } ) ;
// here plainUser will be equal to
// { id: 1, name: "Johny Cage" }
グループを使用して、どのデータを公開し、どのデータを公開しないかを制御できます。
import { Exclude , Expose , instanceToPlain } from 'class-transformer' ;
export class User {
id : number ;
name : string ;
@ Expose ( { groups : [ 'user' , 'admin' ] } ) // this means that this data will be exposed only to users and admins
email : string ;
@ Expose ( { groups : [ 'user' ] } ) // this means that this data will be exposed only to users
password : string ;
}
let user1 = instanceToPlain ( user , { groups : [ 'user' ] } ) ; // will contain id, name, email and password
let user2 = instanceToPlain ( user , { groups : [ 'admin' ] } ) ; // will contain id, name and email
異なるバージョンの API を構築している場合、class-transformer にはそのための非常に便利なツールがあります。モデルのどのプロパティをどのバージョンで公開または除外するかを制御できます。例:
import { Exclude , Expose , instanceToPlain } from 'class-transformer' ;
export class User {
id : number ;
name : string ;
@ Expose ( { since : 0.7 , until : 1 } ) // this means that this property will be exposed for version starting from 0.7 until 1
email : string ;
@ Expose ( { since : 2.1 } ) // this means that this property will be exposed for version starting from 2.1
password : string ;
}
let user1 = instanceToPlain ( user , { version : 0.5 } ) ; // will contain id and name
let user2 = instanceToPlain ( user , { version : 0.7 } ) ; // will contain id, name and email
let user3 = instanceToPlain ( user , { version : 1 } ) ; // will contain id and name
let user4 = instanceToPlain ( user , { version : 2 } ) ; // will contain id and name
let user5 = instanceToPlain ( user , { version : 2.1 } ) ; // will contain id, name and password
プレーンな JavaScript オブジェクトの日付が文字列形式で受け取られる場合があります。そして、そこから実際の JavaScript Date オブジェクトを作成したいとします。 Date オブジェクトを@Type
デコレータに渡すだけでこれを行うことができます。
import { Type } from 'class-transformer' ;
export class User {
id : number ;
email : string ;
password : string ;
@ Type ( ( ) => Date )
registrationDate : Date ;
}
Number
、 String
、 Boolean
プリミティブ型で値をこれらの型に変換する場合は、同じ手法を使用できます。
配列を使用する場合は、配列に含まれるオブジェクトの型を指定する必要があります。この型は、 @Type()
デコレータで指定します。
import { Type } from 'class-transformer' ;
export class Photo {
id : number ;
name : string ;
@ Type ( ( ) => Album )
albums : Album [ ] ;
}
カスタム配列タイプを使用することもできます。
import { Type } from 'class-transformer' ;
export class AlbumCollection extends Array < Album > {
// custom array functions ...
}
export class Photo {
id : number ;
name : string ;
@ Type ( ( ) => Album )
albums : AlbumCollection ;
}
ライブラリは適切な変換を自動的に処理します。
ES6 コレクションSet
とMap
も@Type
デコレータが必要です。
export class Skill {
name : string ;
}
export class Weapon {
name : string ;
range : number ;
}
export class Player {
name : string ;
@ Type ( ( ) => Skill )
skills : Set < Skill > ;
@ Type ( ( ) => Weapon )
weapons : Map < string , Weapon > ;
}
@Transform
デコレーターを使用して追加のデータ変換を実行できます。たとえば、オブジェクトをプレーンからクラスに変換するときに、 Date
オブジェクトをmoment
オブジェクトにしたいとします。
import { Transform } from 'class-transformer' ;
import * as moment from 'moment' ;
import { Moment } from 'moment' ;
export class Photo {
id : number ;
@ Type ( ( ) => Date )
@ Transform ( ( { value } ) => moment ( value ) , { toClassOnly : true } )
date : Moment ;
}
ここで、 plainToInstance
呼び出して Photo オブジェクトのプレーン表現を送信すると、写真オブジェクトの日付値が瞬間の日付に変換されます。 @Transform
デコレータはグループとバージョン管理もサポートしています。
@Transform
デコレーターには、変換の実行方法を構成できるようにするためのより多くの引数が与えられます。
@ Transform ( ( { value , key , obj , type } ) => value )
口論 | 説明 |
---|---|
value | 変換前のプロパティ値。 |
key | 変換されたプロパティの名前。 |
obj | 変換元オブジェクト。 |
type | 変換タイプ。 |
options | 変換メソッドに渡されるオプション オブジェクト。 |
サイン | 例 | 説明 |
---|---|---|
@TransformClassToPlain | @TransformClassToPlain({ groups: ["user"] }) | メソッドの戻り値をinstanceToPlainで変換し、クラスのプロパティを公開します。 |
@TransformClassToClass | @TransformClassToClass({ groups: ["user"] }) | メソッドの戻り値をinstanceToInstanceで変換し、クラスのプロパティを公開します。 |
@TransformPlainToClass | @TransformPlainToClass(User, { groups: ["user"] }) | plainToInstance を使用してメソッドの戻り値を変換し、クラスのプロパティを公開します。 |
上記のデコレータは、オプションの引数を 1 つ受け入れます: ClassTransformOptions - グループ、バージョン、名前などの変換オプション
例:
@ Exclude ( )
class User {
id : number ;
@ Expose ( )
firstName : string ;
@ Expose ( )
lastName : string ;
@ Expose ( { groups : [ 'user.email' ] } )
email : string ;
password : string ;
}
class UserController {
@ TransformClassToPlain ( { groups : [ 'user.email' ] } )
getUser ( ) {
const user = new User ( ) ;
user . firstName = 'Snir' ;
user . lastName = 'Segal' ;
user . password = 'imnosuperman' ;
return user ;
}
}
const controller = new UserController ( ) ;
const user = controller . getUser ( ) ;
これらは公開変数であるため、 user
変数には firstName、lastName、email プロパティのみが含まれます。グループ「user.email」について言及したため、email プロパティも公開されます。
TypeScript にはまだ適切なリフレクション機能がないため、ジェネリックスはサポートされていません。 TypeScript チームがより優れたランタイム型リフレクション ツールを提供したら、ジェネリックスが実装されるでしょう。ただし、問題を解決できる可能性のある調整がいくつかあります。この例を確認してください。
注: class-validator を class-transformer と一緒に使用する場合は、おそらくこの機能を有効にしたくないでしょう。
Typescript によって提供される型情報に基づいて、組み込み型間の自動変換を有効にします。デフォルトでは無効になっています。
import { IsString } from 'class-validator' ;
class MyPayload {
@ IsString ( )
prop : string ;
}
const result1 = plainToInstance ( MyPayload , { prop : 1234 } , { enableImplicitConversion : true } ) ;
const result2 = plainToInstance ( MyPayload , { prop : 1234 } , { enableImplicitConversion : false } ) ;
/**
* result1 will be `{ prop: "1234" }` - notice how the prop value has been converted to string.
* result2 will be `{ prop: 1234 }` - default behaviour
*/
循環参照は無視されます。たとえば、 Photo
タイプのプロパティphotos
を含むクラスUser
変換し、 Photo
その親User
へのリンクuser
含まれる場合、 user
変換中に無視されます。循環参照は、 instanceToInstance
操作中にのみ無視されません。
ユーザーをダウンロードし、それらをUser
クラスのインスタンスに自動的にマッピングしたいとします。
import { plainToInstance } from 'class-transformer' ;
this . http
. get ( 'users.json' )
. map ( res => res . json ( ) )
. map ( res => plainToInstance ( User , res as Object [ ] ) )
. subscribe ( users => {
// now "users" is type of User[] and each user has getName() and isAdult() methods available
console . log ( users ) ;
} ) ;
また、クラスClassTransformer
providers
のサービスとして挿入し、そのメソッドを使用することもできます。
prunker で angular 2 を使用する例。ソースコードはここにあります。
その他の使用例については、./sample のサンプルを参照してください。
重大な変更とリリースノートに関する情報はここで参照してください。