Skip to content

Commit 6fc9cc5

Browse files
author
guscastro
committed
Implement circular checks using DFS recursion stack method
1 parent c3d0030 commit 6fc9cc5

File tree

2 files changed

+31
-10
lines changed

2 files changed

+31
-10
lines changed

src/TransformOperationExecutor.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export class TransformOperationExecutor {
1414
// Private Properties
1515
// -------------------------------------------------------------------------
1616

17-
private transformedTypesMap = new Map<Object, {level: number, object: Object}>();
17+
private recursionStack = new Set<Object>();
1818

1919
// -------------------------------------------------------------------------
2020
// Constructor
@@ -39,7 +39,7 @@ export class TransformOperationExecutor {
3939
const newValue = arrayType && this.transformationType === TransformationType.PLAIN_TO_CLASS ? new (arrayType as any)() : [];
4040
(value as any[]).forEach((subValue, index) => {
4141
const subSource = source ? source[index] : undefined;
42-
if (!this.options.enableCircularCheck || !this.isCircular(subValue, level)) {
42+
if (!this.options.enableCircularCheck || !this.isCircular(subValue)) {
4343
const value = this.transform(subSource, subValue, targetType, undefined, subValue instanceof Map, level + 1);
4444
if (newValue instanceof Set) {
4545
newValue.add(value);
@@ -82,7 +82,7 @@ export class TransformOperationExecutor {
8282

8383
if (this.options.enableCircularCheck) {
8484
// add transformed type to prevent circular references
85-
this.transformedTypesMap.set(value, {level: level, object: value});
85+
this.recursionStack.add(value);
8686
}
8787

8888
const keys = this.getKeys(targetType, value);
@@ -162,7 +162,7 @@ export class TransformOperationExecutor {
162162
continue;
163163
}
164164

165-
if (!this.options.enableCircularCheck || !this.isCircular(subValue, level)) {
165+
if (!this.options.enableCircularCheck || !this.isCircular(subValue)) {
166166
let transformKey = this.transformationType === TransformationType.PLAIN_TO_CLASS ? newValueKey : key;
167167
let finalValue = this.transform(subSource, subValue, type, arrayType, isSubValueMap, level + 1);
168168
finalValue = this.applyCustomTransformations(finalValue, targetType, transformKey, value, this.transformationType);
@@ -182,6 +182,11 @@ export class TransformOperationExecutor {
182182
}
183183

184184
}
185+
186+
if (this.options.enableCircularCheck) {
187+
this.recursionStack.delete(value);
188+
}
189+
185190
return newValue;
186191

187192
} else {
@@ -224,9 +229,8 @@ export class TransformOperationExecutor {
224229
}
225230

226231
// preventing circular references
227-
private isCircular(object: Object, level: number) {
228-
const transformed = this.transformedTypesMap.get(object);
229-
return transformed !== undefined && transformed.level < level;
232+
private isCircular(object: Object) {
233+
return this.recursionStack.has(object);
230234
}
231235

232236
private getReflectedType(target: Function, propertyName: string) {

test/functional/circular-reference-problem.spec.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,26 @@ import * as sinon from "sinon";
77

88
describe("circular reference problem", () => {
99

10-
it("should skip circular reference objects", () => {
10+
it("should skip circular reference objects in classToPlain operation", () => {
1111
defaultMetadataStorage.clear();
1212

13+
class Caption {
14+
text: string;
15+
}
16+
1317
class Photo {
1418
id: number;
1519
filename: string;
1620
user: User;
1721
users: User[];
22+
23+
caption: Caption;
1824
}
1925

2026
class User {
2127
id: number;
2228
firstName: string;
29+
caption: Caption;
2330
photos: Photo[];
2431
}
2532

@@ -31,7 +38,11 @@ describe("circular reference problem", () => {
3138
photo2.id = 2;
3239
photo2.filename = "she.jpg";
3340

41+
const caption = new Caption();
42+
caption.text = "cool photo";
43+
3444
const user = new User();
45+
user.caption = caption;
3546
user.firstName = "Umed Khudoiberdiev";
3647
user.photos = [photo1, photo2];
3748

@@ -40,17 +51,23 @@ describe("circular reference problem", () => {
4051
photo1.users = [user];
4152
photo2.users = [user];
4253

54+
photo1.caption = caption;
55+
photo2.caption = caption;
56+
4357
const plainUser = classToPlain(user, { enableCircularCheck: true });
4458
plainUser.should.be.eql({
4559
firstName: "Umed Khudoiberdiev",
60+
caption: { text: "cool photo" },
4661
photos: [{
4762
id: 1,
4863
filename: "me.jpg",
49-
users: []
64+
users: [],
65+
caption: { text: "cool photo" }
5066
}, {
5167
id: 2,
5268
filename: "she.jpg",
53-
users: []
69+
users: [],
70+
caption: { text: "cool photo" }
5471
}]
5572
});
5673

0 commit comments

Comments
 (0)