Ini era ES6 dan TypeScript. Saat ini Anda bekerja dengan kelas dan objek konstruktor lebih dari sebelumnya. Transformator kelas memungkinkan Anda mengubah objek biasa menjadi beberapa instance kelas dan sebaliknya. Juga memungkinkan untuk membuat serialisasi/deserialisasi objek berdasarkan kriteria. Alat ini sangat berguna di frontend dan backend.
Contoh cara menggunakan dengan sudut 2 di plunker. Kode sumber tersedia di sini.
Dalam JavaScript ada dua jenis objek:
Objek biasa adalah objek yang merupakan turunan dari kelas Object
. Terkadang disebut objek literal , bila dibuat melalui notasi {}
. Objek kelas adalah turunan kelas dengan konstruktor, properti, dan metode yang ditentukan sendiri. Biasanya Anda mendefinisikannya melalui notasi class
.
Jadi, apa masalahnya?
Terkadang Anda ingin mengubah objek javascript biasa menjadi kelas ES6 yang Anda miliki. Misalnya, jika Anda memuat json dari backend Anda, beberapa api atau dari file json, dan setelah Anda JSON.parse
, Anda memiliki objek javascript biasa, bukan turunan kelas yang Anda miliki.
Misalnya Anda memiliki daftar pengguna di users.json
yang Anda muat:
[
{
"id" : 1 ,
"firstName" : " Johny " ,
"lastName" : " Cage " ,
"age" : 27
},
{
"id" : 2 ,
"firstName" : " Ismoil " ,
"lastName" : " Somoni " ,
"age" : 50
},
{
"id" : 3 ,
"firstName" : " Luke " ,
"lastName" : " Dacascos " ,
"age" : 12
}
]
Dan Anda memiliki kelas 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 ;
}
}
Anda berasumsi bahwa Anda mengunduh pengguna bertipe User
dari file users.json
dan mungkin ingin menulis kode berikut:
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
} ) ;
Dalam kode ini Anda dapat menggunakan users[0].id
, Anda juga dapat menggunakan users[0].firstName
dan users[0].lastName
. Namun Anda tidak dapat menggunakan users[0].getName()
atau users[0].isAdult()
karena "pengguna" sebenarnya adalah larik objek javascript biasa, bukan turunan dari objek Pengguna. Anda sebenarnya berbohong kepada kompiler ketika Anda mengatakan bahwa users: User[]
.
Jadi apa yang harus dilakukan? Bagaimana cara membuat array users
dari objek User
alih-alih objek javascript biasa? Solusinya adalah membuat instance baru dari objek Pengguna dan secara manual menyalin semua properti ke objek baru. Namun segala sesuatunya mungkin menjadi salah dengan sangat cepat setelah Anda memiliki hierarki objek yang lebih kompleks.
Alternatif? Ya, Anda dapat menggunakan transformator kelas. Tujuan perpustakaan ini adalah untuk membantu Anda memetakan objek javascript biasa ke instance kelas yang Anda miliki.
Pustaka ini juga bagus untuk model yang diekspos di API Anda, karena pustaka ini menyediakan alat yang hebat untuk mengontrol apa yang diekspos model Anda di API Anda. Berikut ini contoh tampilannya:
fetch ( 'users.json' ) . then ( ( users : Object [ ] ) => {
const realUsers = plainToInstance ( User , users ) ;
// now each user in realUsers is an instance of User class
} ) ;
Sekarang Anda dapat menggunakan metode users[0].getName()
dan users[0].isAdult()
.
Instal modul:
npm install class-transformer --save
shim reflect-metadata
diperlukan, instal juga:
npm install reflect-metadata --save
dan pastikan untuk mengimpornya di tempat global, seperti app.ts:
import 'reflect-metadata' ;
Fitur ES6 digunakan, jika Anda menggunakan node.js versi lama, Anda mungkin perlu menginstal es6-shim:
npm install es6-shim --save
dan mengimpornya di tempat global seperti app.ts:
import 'es6-shim' ;
Instal modul:
npm install class-transformer --save
shim reflect-metadata
diperlukan, instal juga:
npm install reflect-metadata --save
tambahkan <script>
ke metadata refleksi di kepala index.html
Anda:
< html >
< head >
<!-- ... -->
< script src =" node_modules/reflect-metadata/Reflect.js " > </ script >
</ head >
<!-- ... -->
</ html >
Jika Anda menggunakan sudut 2 Anda seharusnya sudah menginstal shim ini.
Jika Anda menggunakan system.js Anda mungkin ingin menambahkan ini ke konfigurasi map
dan package
:
{
"map" : {
"class-transformer" : " node_modules/class-transformer "
},
"packages" : {
"class-transformer" : { "main" : " index.js " , "defaultExtension" : " js " }
}
}
Metode ini mengubah objek javascript biasa menjadi instance kelas tertentu.
import { plainToInstance } from 'class-transformer' ;
let users = plainToInstance ( User , userJson ) ; // to convert user plain object a single user. also supports arrays
Metode ini mengubah objek biasa menjadi sebuah instance menggunakan Object yang sudah terisi yang merupakan instance dari kelas target.
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.
Metode ini mengubah objek kelas Anda kembali menjadi objek javascript biasa, yang nantinya bisa berupa JSON.stringify
.
import { instanceToPlain } from 'class-transformer' ;
let photo = instanceToPlain ( photo ) ;
Metode ini mengubah objek kelas Anda menjadi instance baru dari objek kelas. Ini mungkin dianggap sebagai tiruan mendalam dari objek Anda.
import { instanceToInstance } from 'class-transformer' ;
let photo = instanceToInstance ( photo ) ;
Anda juga dapat menggunakan opsi ignoreDecorators
dalam opsi transformasi untuk mengabaikan semua dekorator yang digunakan kelas Anda.
Anda dapat membuat serial model Anda langsung ke json menggunakan metode serialize
:
import { serialize } from 'class-transformer' ;
let photo = serialize ( photo ) ;
serialize
berfungsi dengan array dan non-array.
Anda dapat membatalkan serialisasi model Anda dari json menggunakan metode deserialize
:
import { deserialize } from 'class-transformer' ;
let photo = deserialize ( Photo , photo ) ;
Agar deserialisasi berfungsi dengan array, gunakan metode deserializeArray
:
import { deserializeArray } from 'class-transformer' ;
let photos = deserializeArray ( Photo , photos ) ;
Perilaku default metode plainToInstance
adalah menyetel semua properti dari objek biasa, bahkan properti yang tidak ditentukan di kelas.
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',
// }
Jika perilaku ini tidak sesuai dengan kebutuhan Anda, Anda dapat menggunakan opsi excludeExtraneousValues
dalam metode plainToInstance
sambil mengekspos semua properti kelas Anda sebagai persyaratan.
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'
// }
Saat Anda mencoba mengubah objek yang memiliki objek bersarang, Anda perlu mengetahui jenis objek apa yang ingin Anda ubah. Karena TypeScript belum memiliki kemampuan refleksi yang baik, kita harus secara implisit menentukan jenis objek yang dimiliki setiap properti. Ini dilakukan dengan menggunakan dekorator @Type
.
Katakanlah kita memiliki album dengan foto. Dan kami mencoba mengonversi objek biasa album menjadi objek kelas:
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
Jika objek yang disarangkan dapat memiliki tipe yang berbeda, Anda dapat menyediakan objek opsi tambahan, yang menentukan diskriminator. Opsi diskriminator harus menentukan property
yang menyimpan nama subtipe untuk objek dan kemungkinan subTypes
yang dapat dikonversi menjadi objek bersarang. Sub tipe memiliki value
, yang menampung konstruktor Type dan name
, yang dapat dicocokkan dengan property
diskriminator.
Katakanlah kita memiliki album yang memiliki foto teratas. Namun foto ini bisa saja jenisnya berbeda-beda. Dan kami mencoba mengubah objek biasa album menjadi objek kelas. Input objek biasa harus mendefinisikan properti tambahan __type
. Properti ini dihapus selama transformasi secara default:
Masukan 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.
Petunjuk: Hal yang sama berlaku untuk array dengan subtipe yang berbeda. Selain itu, Anda dapat menentukan keepDiscriminatorProperty: true
dalam opsi untuk mempertahankan properti diskriminator juga di dalam kelas yang Anda hasilkan.
Anda dapat mengekspos apa yang dihasilkan oleh pengambil atau metode Anda dengan menyetel dekorator @Expose()
ke pengambil atau metode tersebut:
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 ;
}
}
Jika Anda ingin mengekspos beberapa properti dengan nama berbeda, Anda dapat melakukannya dengan menentukan opsi name
ke dekorator @Expose
:
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 ;
}
}
Terkadang Anda ingin melewatkan beberapa properti selama transformasi. Ini dapat dilakukan menggunakan dekorator @Exclude
:
import { Exclude } from 'class-transformer' ;
export class User {
id : number ;
email : string ;
@ Exclude ( )
password : string ;
}
Sekarang ketika Anda mengubah Pengguna, properti password
akan dilewati dan tidak disertakan dalam hasil transformasi.
Anda dapat mengontrol operasi apa yang akan Anda kecualikan suatu properti. Gunakan opsi toClassOnly
atau toPlainOnly
:
import { Exclude } from 'class-transformer' ;
export class User {
id : number ;
email : string ;
@ Exclude ( { toPlainOnly : true } )
password : string ;
}
Sekarang properti password
akan dikecualikan hanya selama operasi instanceToPlain
. Sebaliknya, gunakan opsi toClassOnly
.
Anda dapat melewati semua properti kelas, dan hanya mengekspos properti yang diperlukan secara eksplisit:
import { Exclude , Expose } from 'class-transformer' ;
@ Exclude ( )
export class User {
@ Expose ( )
id : number ;
@ Expose ( )
email : string ;
password : string ;
}
Sekarang id
dan email
akan diekspos, dan kata sandi akan dikecualikan selama transformasi. Alternatifnya, Anda dapat menetapkan strategi pengecualian selama transformasi:
import { instanceToPlain } from 'class-transformer' ;
let photo = instanceToPlain ( photo , { strategy : 'excludeAll' } ) ;
Dalam hal ini Anda tidak perlu @Exclude()
seluruh kelas.
Jika Anda memberi nama properti pribadi Anda dengan awalan, katakanlah dengan _
, maka Anda juga dapat mengecualikan properti tersebut dari transformasi:
import { instanceToPlain } from 'class-transformer' ;
let photo = instanceToPlain ( photo , { excludePrefixes : [ '_' ] } ) ;
Ini akan melewatkan semua properti yang dimulai dengan awalan _
. Anda dapat meneruskan sejumlah awalan dan semua properti yang dimulai dengan awalan ini akan diabaikan. Misalnya:
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" }
Anda dapat menggunakan grup untuk mengontrol data apa yang akan diekspos dan apa yang tidak:
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
Jika Anda membuat API yang memiliki versi berbeda, class-transformer memiliki alat yang sangat berguna untuk itu. Anda dapat mengontrol properti model mana yang harus diekspos atau dikecualikan dalam versi apa. Contoh:
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
Terkadang Anda memiliki Tanggal di objek javascript biasa yang diterima dalam format string. Dan Anda ingin membuat objek Tanggal javascript nyata darinya. Anda dapat melakukannya hanya dengan meneruskan objek Date ke dekorator @Type
:
import { Type } from 'class-transformer' ;
export class User {
id : number ;
email : string ;
password : string ;
@ Type ( ( ) => Date )
registrationDate : Date ;
}
Teknik yang sama dapat digunakan dengan tipe primitif Number
, String
, Boolean
ketika Anda ingin mengonversi nilai Anda ke dalam tipe ini.
Saat Anda menggunakan array, Anda harus menyediakan tipe objek yang berisi array. Tipe ini, Anda tentukan di dekorator @Type()
:
import { Type } from 'class-transformer' ;
export class Photo {
id : number ;
name : string ;
@ Type ( ( ) => Album )
albums : Album [ ] ;
}
Anda juga dapat menggunakan tipe array khusus:
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 ;
}
Perpustakaan akan menangani transformasi yang tepat secara otomatis.
Koleksi ES6 Set
dan Map
juga memerlukan dekorator @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 > ;
}
Anda dapat melakukan transformasi data tambahan menggunakan dekorator @Transform
. Misalnya, Anda ingin menjadikan objek Date
menjadi objek moment
ketika Anda mengubah objek dari biasa menjadi kelas:
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 ;
}
Sekarang saat Anda memanggil plainToInstance
dan mengirimkan representasi polos objek Foto, nilai tanggal di objek foto Anda akan diubah menjadi tanggal momen. @Transform
dekorator juga mendukung grup dan pembuatan versi.
Dekorator @Transform
diberikan lebih banyak argumen untuk memungkinkan Anda mengonfigurasi cara transformasi yang Anda inginkan dilakukan.
@ Transform ( ( { value , key , obj , type } ) => value )
Argumen | Keterangan |
---|---|
value | Nilai properti sebelum transformasi. |
key | Nama properti yang diubah. |
obj | Objek sumber transformasi. |
type | Tipe transformasi. |
options | Objek opsi diteruskan ke metode transformasi. |
Tanda tangan | Contoh | Keterangan |
---|---|---|
@TransformClassToPlain | @TransformClassToPlain({ groups: ["user"] }) | Ubah pengembalian metode dengan instanceToPlain dan ekspos properti di kelas. |
@TransformClassToClass | @TransformClassToClass({ groups: ["user"] }) | Ubah pengembalian metode dengan instanceToInstance dan ekspos properti di kelas. |
@TransformPlainToClass | @TransformPlainToClass(User, { groups: ["user"] }) | Ubah pengembalian metode dengan plainToInstance dan ekspos properti di kelas. |
Dekorator di atas menerima satu argumen opsional: ClassTransformOptions - Opsi transformasi seperti grup, versi, nama
Contoh:
@ 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 ( ) ;
variabel user
hanya akan berisi properti firstName,lastName, email karena mereka adalah variabel yang terbuka. properti email juga terekspos karena kami menyebut grup "user.email".
Generik tidak didukung karena TypeScript belum memiliki kemampuan refleksi yang baik. Setelah tim TypeScript memberi kami alat refleksi tipe runtime yang lebih baik, obat generik akan diimplementasikan. Ada beberapa penyesuaian yang dapat Anda gunakan, yang mungkin dapat menyelesaikan masalah Anda. Lihat contoh ini.
CATATAN Jika Anda menggunakan validator kelas bersama dengan transformator kelas, Anda mungkin TIDAK ingin mengaktifkan fungsi ini.
Mengaktifkan konversi otomatis antar tipe bawaan berdasarkan informasi tipe yang disediakan oleh TypeScript. Dinonaktifkan secara default.
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
*/
Referensi melingkar diabaikan. Misalnya, jika Anda mentransformasikan kelas User
yang berisi properti photos
dengan tipe Photo
, dan Photo
berisi link user
ke induknya User
, maka user
akan diabaikan selama transformasi. Referensi melingkar tidak diabaikan hanya selama operasi instanceToInstance
.
Katakanlah Anda ingin mengunduh pengguna dan ingin mereka secara otomatis dipetakan ke instance kelas 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 ) ;
} ) ;
Anda juga dapat memasukkan kelas ClassTransformer
sebagai layanan di providers
, dan menggunakan metodenya.
Contoh cara menggunakan dengan sudut 2 di plunker. Kode sumber ada di sini.
Lihat contoh di ./sample untuk contoh penggunaan lainnya.
Lihat informasi tentang perubahan penting dan catatan rilis di sini.