Skip to content

Commit 5e76ab6

Browse files
author
Umed Khudoiberdiev
authored
Merge pull request #38 from snirs90/feat/json-view
Adding JsonView decorator
2 parents 48a6ef2 + e3f8f2f commit 5e76ab6

File tree

4 files changed

+313
-1
lines changed

4 files changed

+313
-1
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
build/
22
node_modules/
33
coverage/
4-
npm-debug.log
4+
npm-debug.log
5+
.idea
6+
.vscode

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,55 @@ Now when you call `plainToClass` and send a plain representation of the Photo ob
590590
it will convert a date value in your photo object to moment date.
591591
`@Transform` decorator also supports groups and versioning.
592592

593+
## Other decorators
594+
| Signature | Example | Description
595+
|--------------------|------------------------------------------|---------------------------------------------|
596+
| `@TransformClassToPlain` | `@TransformClassToPlain({ groups: ["user"] })` | Transform the method return with classToPlain and expose the properties on the class.
597+
| `@TransformClassToClass` | `@TransformClassToClass({ groups: ["user"] })` | Transform the method return with classToClass and expose the properties on the class.
598+
599+
The above decorators accept one optional argument:
600+
ClassTransformOptions - The transform options like groups, version, name
601+
602+
An example:
603+
604+
```typescript
605+
@Exclude()
606+
class User {
607+
608+
id: number;
609+
610+
@Expose()
611+
firstName: string;
612+
613+
@Expose()
614+
lastName: string;
615+
616+
@Expose({ groups: ['user.email'] })
617+
email: string;
618+
619+
password: string;
620+
}
621+
622+
class UserController {
623+
624+
@TransformClassToPlain({ groups: ['user.email'] })
625+
getUser() {
626+
const user = new User();
627+
user.firstName = "Snir";
628+
user.lastName = "Segal";
629+
user.password = "imnosuperman";
630+
631+
return user;
632+
}
633+
}
634+
635+
const controller = new UserController();
636+
const user = controller.getUser();
637+
```
638+
639+
the `user` variable will contain only firstName,lastName, email properties becuase they are
640+
the exposed variables. email property is also exposed becuase we metioned the group "user.email".
641+
593642
## Working with generics
594643

595644
Generics are not supported because TypeScript does not have good reflection abilities yet.

src/decorators.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import {ClassTransformer} from "./ClassTransformer";
12
import {defaultMetadataStorage} from "./storage";
23
import {TypeMetadata} from "./metadata/TypeMetadata";
34
import {ExposeMetadata} from "./metadata/ExposeMetadata";
45
import {ExposeOptions, ExcludeOptions, TypeOptions, TransformOptions} from "./metadata/ExposeExcludeOptions";
56
import {ExcludeMetadata} from "./metadata/ExcludeMetadata";
67
import {TransformMetadata} from "./metadata/TransformMetadata";
8+
import {ClassTransformOptions} from "./ClassTransformOptions";
79

810
/**
911
* Defines a custom logic for value transformation.
@@ -48,4 +50,40 @@ export function Exclude(options?: ExcludeOptions) {
4850
const metadata = new ExcludeMetadata(object instanceof Function ? object : object.constructor, propertyName, options || {});
4951
defaultMetadataStorage.addExcludeMetadata(metadata);
5052
};
53+
}
54+
55+
/**
56+
* Transform the object from class to plain object and return only with the exposed properties.
57+
*/
58+
export function TransformClassToPlain(params?: ClassTransformOptions): Function {
59+
60+
return function (target: Function, propertyKey: string, descriptor: PropertyDescriptor) {
61+
const classTransformer: ClassTransformer = new ClassTransformer();
62+
const originalMethod = descriptor.value;
63+
64+
descriptor.value = function(...args: any[]) {
65+
const result: any = originalMethod.apply(this, args);
66+
const isPromise = !!result && (typeof result === "object" || typeof result === "function") && typeof result.then === "function";
67+
68+
return isPromise ? result.then((data: any) => classTransformer.classToPlain(data, params)) : classTransformer.classToPlain(result, params);
69+
};
70+
};
71+
}
72+
73+
/**
74+
* Return the class instance only with the exposed properties.
75+
*/
76+
export function TransformClassToClass(params?: ClassTransformOptions): Function {
77+
78+
return function (target: Function, propertyKey: string, descriptor: PropertyDescriptor) {
79+
const classTransformer: ClassTransformer = new ClassTransformer();
80+
const originalMethod = descriptor.value;
81+
82+
descriptor.value = function(...args: any[]) {
83+
const result: any = originalMethod.apply(this, args);
84+
const isPromise = !!result && (typeof result === "object" || typeof result === "function") && typeof result.then === "function";
85+
86+
return isPromise ? result.then((data: any) => classTransformer.classToClass(data, params)) : classTransformer.classToClass(result, params);
87+
};
88+
};
5189
}
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
import "reflect-metadata";
2+
import {defaultMetadataStorage} from "../../src/storage";
3+
import {Exclude, Expose, TransformClassToPlain, TransformClassToClass} from "../../src/decorators";
4+
import {expect} from "chai";
5+
6+
describe("transformer methods decorator", () => {
7+
8+
it("should expose non configuration properties and return User instance class", () => {
9+
defaultMetadataStorage.clear();
10+
11+
@Exclude()
12+
class User {
13+
14+
id: number;
15+
16+
@Expose()
17+
firstName: string;
18+
19+
@Expose()
20+
lastName: string;
21+
22+
password: string;
23+
}
24+
25+
class UserController {
26+
27+
@TransformClassToClass()
28+
getUser() {
29+
const user = new User();
30+
user.firstName = "Snir";
31+
user.lastName = "Segal";
32+
user.password = "imnosuperman";
33+
34+
return user;
35+
}
36+
}
37+
38+
const controller = new UserController();
39+
40+
const result = controller.getUser();
41+
expect(result.password).to.be.undefined;
42+
43+
const plainUser = {
44+
firstName: "Snir",
45+
lastName: "Segal"
46+
};
47+
48+
49+
expect(result).to.be.eql(plainUser);
50+
expect(result).to.be.instanceof(User);
51+
});
52+
53+
it("should expose non configuration properties", () => {
54+
defaultMetadataStorage.clear();
55+
56+
@Exclude()
57+
class User {
58+
59+
id: number;
60+
61+
@Expose()
62+
firstName: string;
63+
64+
@Expose()
65+
lastName: string;
66+
67+
password: string;
68+
}
69+
70+
class UserController {
71+
72+
@TransformClassToPlain()
73+
getUser() {
74+
const user = new User();
75+
user.firstName = "Snir";
76+
user.lastName = "Segal";
77+
user.password = "imnosuperman";
78+
79+
return user;
80+
}
81+
}
82+
83+
const controller = new UserController();
84+
85+
const result = controller.getUser();
86+
expect(result.password).to.be.undefined;
87+
88+
const plainUser = {
89+
firstName: "Snir",
90+
lastName: "Segal"
91+
};
92+
93+
expect(result).to.be.eql(plainUser);
94+
});
95+
96+
it("should expose non configuration properties and properties with specific groups", () => {
97+
defaultMetadataStorage.clear();
98+
99+
@Exclude()
100+
class User {
101+
102+
id: number;
103+
104+
@Expose()
105+
firstName: string;
106+
107+
@Expose()
108+
lastName: string;
109+
110+
@Expose({ groups: ["user.permissions"] })
111+
roles: string[];
112+
113+
password: string;
114+
}
115+
116+
class UserController {
117+
118+
@TransformClassToPlain({ groups: ["user.permissions"] })
119+
getUserWithRoles() {
120+
const user = new User();
121+
user.firstName = "Snir";
122+
user.lastName = "Segal";
123+
user.password = "imnosuperman";
124+
user.roles = ["USER", "MANAGER"];
125+
126+
return user;
127+
}
128+
129+
}
130+
131+
const controller = new UserController();
132+
133+
const result = controller.getUserWithRoles();
134+
expect(result.password).to.be.undefined;
135+
136+
const plainUser = {
137+
firstName: "Snir",
138+
lastName: "Segal",
139+
roles: ["USER", "MANAGER"]
140+
};
141+
142+
expect(result).to.be.eql(plainUser);
143+
});
144+
145+
it("should expose non configuration properties with specific version", () => {
146+
defaultMetadataStorage.clear();
147+
148+
@Exclude()
149+
class User {
150+
151+
id: number;
152+
153+
@Expose()
154+
firstName: string;
155+
156+
@Expose()
157+
lastName: string;
158+
159+
@Expose({ groups: ["user.permissions"] })
160+
roles: string[];
161+
162+
@Expose({ since: 2 })
163+
websiteUrl?: string;
164+
165+
password: string;
166+
}
167+
168+
class UserController {
169+
170+
@TransformClassToPlain({ version: 1 })
171+
getUserVersion1() {
172+
const user = new User();
173+
user.firstName = "Snir";
174+
user.lastName = "Segal";
175+
user.password = "imnosuperman";
176+
user.roles = ["USER", "MANAGER"];
177+
user.websiteUrl = "http://www.github.com";
178+
179+
return user;
180+
}
181+
182+
@TransformClassToPlain({ version: 2 })
183+
getUserVersion2() {
184+
const user = new User();
185+
user.firstName = "Snir";
186+
user.lastName = "Segal";
187+
user.password = "imnosuperman";
188+
user.roles = ["USER", "MANAGER"];
189+
user.websiteUrl = "http://www.github.com";
190+
191+
return user;
192+
}
193+
194+
}
195+
196+
const controller = new UserController();
197+
198+
const resultV2 = controller.getUserVersion2();
199+
expect(resultV2.password).to.be.undefined;
200+
expect(resultV2.roles).to.be.undefined;
201+
202+
const plainUserV2 = {
203+
firstName: "Snir",
204+
lastName: "Segal",
205+
websiteUrl: "http://www.github.com"
206+
};
207+
208+
expect(resultV2).to.be.eql(plainUserV2);
209+
210+
const resultV1 = controller.getUserVersion1();
211+
expect(resultV1.password).to.be.undefined;
212+
expect(resultV1.roles).to.be.undefined;
213+
expect(resultV1.websiteUrl).to.be.undefined;
214+
215+
const plainUserV1 = {
216+
firstName: "Snir",
217+
lastName: "Segal"
218+
};
219+
220+
expect(resultV1).to.be.eql(plainUserV1);
221+
});
222+
223+
});

0 commit comments

Comments
 (0)