ES6와 Typescript 시대입니다. 요즘에는 그 어느 때보다 클래스와 생성자 객체를 사용하여 작업하고 있습니다. 클래스 변환기를 사용하면 일반 객체를 클래스의 일부 인스턴스로 또는 그 반대로 변환할 수 있습니다. 또한 기준에 따라 객체를 직렬화/역직렬화할 수 있습니다. 이 도구는 프런트엔드와 백엔드 모두에서 매우 유용합니다.
플런커에서 각도 2를 사용하는 방법의 예입니다. 소스 코드는 여기에서 확인할 수 있습니다.
JavaScript에는 두 가지 유형의 객체가 있습니다.
일반 객체는 Object
클래스의 인스턴스인 객체입니다. 때로는 {}
표기법을 통해 생성될 때 리터럴 객체라고 합니다. 클래스 객체는 자체 정의된 생성자, 속성 및 메서드가 있는 클래스의 인스턴스입니다. 일반적으로 class
표기법을 통해 정의합니다.
그렇다면 문제는 무엇입니까?
때로는 일반 자바스크립트 객체를 가지고 있는 ES6 클래스 로 변환하고 싶을 때가 있습니다. 예를 들어 백엔드, 일부 API 또는 json 파일에서 json을 로드하는 경우 JSON.parse
후에는 클래스 인스턴스가 아닌 일반 자바스크립트 개체가 생성됩니다.
예를 들어, 로드 중인 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 객체의 인스턴스가 아니라 일반 자바스크립트 객체의 배열이기 때문에 users[0].getName()
또는 users[0].isAdult()
사용할 수 없습니다. users: User[]
라고 말했을 때 실제로 컴파일러에 거짓말을했습니다.
그럼 무엇을 해야 할까요? User
users
의 인스턴스 배열을 만드는 방법은 무엇입니까? 해결 방법은 User 개체의 새 인스턴스를 만들고 모든 속성을 새 개체에 수동으로 복사하는 것입니다. 그러나 개체 계층 구조가 더 복잡해지면 상황이 매우 빠르게 잘못될 수 있습니다.
대안? 예, 클래스 변환기를 사용할 수 있습니다. 이 라이브러리의 목적은 일반 자바스크립트 객체를 가지고 있는 클래스의 인스턴스에 매핑하는 데 도움을 주는 것입니다.
이 라이브러리는 모델이 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
index.html
헤드의 Reflect-metadata에 <script>
추가하세요.
< html >
< head >
<!-- ... -->
< script src =" node_modules/reflect-metadata/Reflect.js " > </ script >
</ head >
<!-- ... -->
</ html >
각도 2를 사용하는 경우 이 심이 이미 설치되어 있어야 합니다.
system.js를 사용하는 경우 이를 map
및 package
구성에 추가할 수 있습니다.
{
"map" : {
"class-transformer" : " node_modules/class-transformer "
},
"packages" : {
"class-transformer" : { "main" : " index.js " , "defaultExtension" : " js " }
}
}
이 메소드는 일반 자바스크립트 객체를 특정 클래스의 인스턴스로 변환합니다.
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.
이 메소드는 클래스 객체를 나중에 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에서 모델을 deserialize할 수 있습니다.
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
중첩된 객체의 유형이 다를 수 있는 경우 판별자를 지정하는 추가 옵션 객체를 제공할 수 있습니다. 판별자 옵션은 객체의 하위 유형 이름과 중첩된 객체가 변환될 수 있는 가능한 subTypes
보유하는 property
정의해야 합니다. 하위 유형에는 판별자의 property
과 일치할 수 있는 Type의 생성자와 name
포함하는 value
있습니다.
최고의 사진이 포함된 앨범이 있다고 가정해 보겠습니다. 하지만 이 사진은 특정 유형이 될 수 있습니다. 그리고 앨범 일반 개체를 클래스 개체로 변환하려고 합니다. 일반 객체 입력은 추가 속성 __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
지정할 수 있습니다.
getter 또는 메서드에 @Expose()
데코레이터를 설정하여 getter 또는 메서드 반환을 노출할 수 있습니다.
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
때로는 일반 자바스크립트 객체에 문자열 형식으로 받은 날짜가 있습니다. 그리고 그것으로부터 실제 자바스크립트 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"] }) | 인스턴스ToPlain을 사용하여 메서드 반환을 변환하고 클래스의 속성을 노출합니다. |
@TransformClassToClass | @TransformClassToClass({ groups: ["user"] }) | instanceToInstance를 사용하여 메서드 반환을 변환하고 클래스의 속성을 노출합니다. |
@TransformPlainToClass | @TransformPlainToClass(User, { groups: ["user"] }) | plainToInstance를 사용하여 메서드 반환을 변환하고 클래스의 속성을 노출합니다. |
위의 데코레이터는 하나의 선택적 인수를 허용합니다. 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 팀이 더 나은 런타임 유형 반영 도구를 제공하면 제네릭이 구현됩니다. 그러나 사용할 수 있는 몇 가지 조정 방법이 있으며 이를 통해 문제를 해결할 수 있습니다. 이 예를 확인해 보세요.
참고 클래스 유효성 검사기를 클래스 변환기와 함께 사용하는 경우 이 기능을 활성화하고 싶지 않을 것입니다.
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 ) ;
} ) ;
또한 providers
의 서비스로 ClassTransformer
클래스를 삽입하고 해당 메서드를 사용할 수도 있습니다.
플런커에서 각도 2를 사용하는 방법의 예입니다. 소스 코드는 여기에 있습니다.
더 많은 사용 예를 보려면 ./sample의 샘플을 살펴보세요.
주요 변경 사항 및 릴리스 노트에 대한 정보는 여기에서 확인하세요.