Skip to content

Commit ff5fcef

Browse files
247GradLabsNoNameProvided
authored andcommitted
perf: storage performance fix (#137)
- improve MetadataStorage perf by changing from Arrays to ES6 Maps by @sheiidan - fixed getAncestor issue with unknown nested properties by @247GradLabs
1 parent c3d0030 commit ff5fcef

File tree

2 files changed

+143
-30
lines changed

2 files changed

+143
-30
lines changed

src/metadata/MetadataStorage.ts

Lines changed: 99 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,45 @@ export class MetadataStorage {
1313
// Properties
1414
// -------------------------------------------------------------------------
1515

16-
private _typeMetadatas: TypeMetadata[] = [];
17-
private _transformMetadatas: TransformMetadata[] = [];
18-
private _exposeMetadatas: ExposeMetadata[] = [];
19-
private _excludeMetadatas: ExcludeMetadata[] = [];
16+
private _typeMetadatas = new Map<Function, Map<string, TypeMetadata>>();
17+
private _transformMetadatas = new Map<Function, Map<string, TransformMetadata[]>>();
18+
private _exposeMetadatas = new Map<Function, Map<string, ExposeMetadata>>();
19+
private _excludeMetadatas = new Map<Function, Map<string, ExcludeMetadata>>();
20+
private _ancestorsMap = new Map<Function, Function[]>();
2021

2122
// -------------------------------------------------------------------------
2223
// Adder Methods
2324
// -------------------------------------------------------------------------
2425

2526
addTypeMetadata(metadata: TypeMetadata) {
26-
this._typeMetadatas.push(metadata);
27+
if (!this._typeMetadatas.has(metadata.target)) {
28+
this._typeMetadatas.set(metadata.target, new Map<string, TypeMetadata>());
29+
}
30+
this._typeMetadatas.get(metadata.target).set(metadata.propertyName, metadata);
2731
}
2832

2933
addTransformMetadata(metadata: TransformMetadata) {
30-
this._transformMetadatas.push(metadata);
34+
if (!this._transformMetadatas.has(metadata.target)) {
35+
this._transformMetadatas.set(metadata.target, new Map<string, TransformMetadata[]>());
36+
}
37+
if (!this._transformMetadatas.get(metadata.target).has(metadata.propertyName)) {
38+
this._transformMetadatas.get(metadata.target).set(metadata.propertyName, []);
39+
}
40+
this._transformMetadatas.get(metadata.target).get(metadata.propertyName).push(metadata);
3141
}
3242

3343
addExposeMetadata(metadata: ExposeMetadata) {
34-
this._exposeMetadatas.push(metadata);
44+
if (!this._exposeMetadatas.has(metadata.target)) {
45+
this._exposeMetadatas.set(metadata.target, new Map<string, ExposeMetadata>());
46+
}
47+
this._exposeMetadatas.get(metadata.target).set(metadata.propertyName, metadata);
3548
}
3649

3750
addExcludeMetadata(metadata: ExcludeMetadata) {
38-
this._excludeMetadatas.push(metadata);
51+
if (!this._excludeMetadatas.has(metadata.target)) {
52+
this._excludeMetadatas.set(metadata.target, new Map<string, ExcludeMetadata>());
53+
}
54+
this._excludeMetadatas.get(metadata.target).set(metadata.propertyName, metadata);
3955
}
4056

4157
// -------------------------------------------------------------------------
@@ -80,8 +96,10 @@ export class MetadataStorage {
8096
}
8197

8298
getStrategy(target: Function): "excludeAll"|"exposeAll"|"none" {
83-
const exclude = this._excludeMetadatas.find(metadata => metadata.target === target && metadata.propertyName === undefined);
84-
const expose = this._exposeMetadatas.find(metadata => metadata.target === target && metadata.propertyName === undefined);
99+
const excludeMap = this._excludeMetadatas.get(target);
100+
const exclude = excludeMap && excludeMap.get(undefined);
101+
const exposeMap = this._exposeMetadatas.get(target);
102+
const expose = exposeMap && exposeMap.get(undefined);
85103
if ((exclude && expose) || (!exclude && !expose)) return "none";
86104
return exclude ? "excludeAll" : "exposeAll";
87105
}
@@ -135,31 +153,82 @@ export class MetadataStorage {
135153
}
136154

137155
clear() {
138-
this._typeMetadatas = [];
139-
this._exposeMetadatas = [];
140-
this._excludeMetadatas = [];
156+
this._typeMetadatas.clear();
157+
this._exposeMetadatas.clear();
158+
this._excludeMetadatas.clear();
159+
this._ancestorsMap.clear();
141160
}
142161

143162
// -------------------------------------------------------------------------
144163
// Private Methods
145164
// -------------------------------------------------------------------------
146165

147-
private getMetadata<T extends { target: Function, propertyName: string }>(metadatas: T[], target: Function): T[] {
148-
const metadataFromTarget = metadatas.filter(meta => meta.target === target && meta.propertyName !== undefined);
149-
const metadataFromChildren = metadatas.filter(meta => target && target.prototype instanceof meta.target && meta.propertyName !== undefined);
150-
return metadataFromChildren.concat(metadataFromTarget);
151-
}
152-
153-
private findMetadata<T extends { target: Function, propertyName: string }>(metadatas: T[], target: Function, propertyName: string): T {
154-
const metadataFromTarget = metadatas.find(meta => meta.target === target && meta.propertyName === propertyName);
155-
const metadataFromChildren = metadatas.find(meta => target && target.prototype instanceof meta.target && meta.propertyName === propertyName);
156-
return metadataFromTarget || metadataFromChildren;
157-
}
158-
159-
private findMetadatas<T extends { target: Function, propertyName: string }>(metadatas: T[], target: Function, propertyName: string): T[] {
160-
const metadataFromTarget = metadatas.filter(meta => meta.target === target && meta.propertyName === propertyName);
161-
const metadataFromChildren = metadatas.filter(meta => target && target.prototype instanceof meta.target && meta.propertyName === propertyName);
162-
return metadataFromChildren.reverse().concat(metadataFromTarget.reverse());
166+
private getMetadata<T extends { target: Function, propertyName: string }>(metadatas: Map<Function, Map<String, T>>, target: Function): T[] {
167+
const metadataFromTargetMap = metadatas.get(target);
168+
let metadataFromTarget: T[];
169+
if (metadataFromTargetMap) {
170+
metadataFromTarget = Array.from(metadataFromTargetMap.values()).filter(meta => meta.propertyName !== undefined);
171+
}
172+
let metadataFromAncestors: T[] = [];
173+
for (const ancestor of this.getAncestors(target)) {
174+
const ancestorMetadataMap = metadatas.get(ancestor);
175+
if (ancestorMetadataMap) {
176+
const metadataFromAncestor = Array.from(ancestorMetadataMap.values()).filter(meta => meta.propertyName !== undefined);
177+
metadataFromAncestors.push(...metadataFromAncestor);
178+
}
179+
}
180+
return metadataFromAncestors.concat(metadataFromTarget || []);
181+
}
182+
183+
private findMetadata<T extends { target: Function, propertyName: string }>(metadatas: Map<Function, Map<string, T>>, target: Function, propertyName: string): T {
184+
const metadataFromTargetMap = metadatas.get(target);
185+
if (metadataFromTargetMap) {
186+
const metadataFromTarget = metadataFromTargetMap.get(propertyName);
187+
if (metadataFromTarget) {
188+
return metadataFromTarget;
189+
}
190+
}
191+
for (const ancestor of this.getAncestors(target)) {
192+
const ancestorMetadataMap = metadatas.get(ancestor);
193+
if (ancestorMetadataMap) {
194+
const ancestorResult = ancestorMetadataMap.get(propertyName);
195+
if (ancestorResult) {
196+
return ancestorResult;
197+
}
198+
}
199+
}
200+
return undefined;
201+
}
202+
203+
private findMetadatas<T extends { target: Function, propertyName: string }>(metadatas: Map<Function, Map<string, T[]>>, target: Function, propertyName: string): T[] {
204+
const metadataFromTargetMap = metadatas.get(target);
205+
let metadataFromTarget: T[];
206+
if (metadataFromTargetMap) {
207+
metadataFromTarget = metadataFromTargetMap.get(propertyName);
208+
}
209+
let metadataFromAncestorsTarget: T[] = [];
210+
for (const ancestor of this.getAncestors(target)) {
211+
const ancestorMetadataMap = metadatas.get(ancestor);
212+
if (ancestorMetadataMap) {
213+
if (ancestorMetadataMap.has(propertyName)) {
214+
metadataFromAncestorsTarget.push(...ancestorMetadataMap.get(propertyName));
215+
}
216+
}
217+
}
218+
return (metadataFromAncestorsTarget).reverse().concat((metadataFromTarget || []).reverse());
219+
}
220+
221+
private getAncestors(target: Function): Function[] {
222+
if (!target) return [];
223+
if (!this._ancestorsMap.has(target)) {
224+
let ancestors: Function[] = [];
225+
for (let baseClass = Object.getPrototypeOf(target.prototype.constructor);
226+
typeof baseClass.prototype !== "undefined";
227+
baseClass = Object.getPrototypeOf(baseClass.prototype.constructor)) {
228+
ancestors.push(baseClass);
229+
}
230+
this._ancestorsMap.set(target, ancestors);
231+
}
232+
return this._ancestorsMap.get(target);
163233
}
164-
165234
}

test/functional/inheritence.spec.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import "reflect-metadata";
2+
import {Transform, Type, plainToClass} from "../../src/index";
3+
import {defaultMetadataStorage} from "../../src/storage";
4+
import {Exclude, Expose} from "../../src/decorators";
5+
6+
describe("inheritence", () => {
7+
8+
it("decorators should work inside a base class", () => {
9+
defaultMetadataStorage.clear();
10+
11+
class Contact {
12+
@Transform(value => value.toUpperCase())
13+
name: string;
14+
@Type(() => Date)
15+
birthDate: Date;
16+
}
17+
18+
class User extends Contact {
19+
@Type(() => Number)
20+
id: number;
21+
email: string;
22+
}
23+
24+
class Student extends User {
25+
@Transform(value => value.toUpperCase())
26+
university: string;
27+
}
28+
29+
let plainStudent = {
30+
name: "Johny Cage",
31+
university: "mit",
32+
birthDate: new Date(1967, 2, 1).toDateString(),
33+
id: 100,
34+
35+
};
36+
37+
const classedStudent = plainToClass(Student, plainStudent);
38+
classedStudent.name.should.be.equal("JOHNY CAGE");
39+
classedStudent.university.should.be.equal("MIT");
40+
classedStudent.birthDate.getTime().should.be.equal(new Date(1967, 2, 1).getTime());
41+
classedStudent.id.should.be.equal(plainStudent.id);
42+
classedStudent.email.should.be.equal(plainStudent.email);
43+
});
44+
});

0 commit comments

Comments
 (0)