Skip to content

Commit b2a0452

Browse files
committed
Adding circularKeys method
1 parent 28e21af commit b2a0452

File tree

5 files changed

+169
-41
lines changed

5 files changed

+169
-41
lines changed

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ console.log(object);
163163

164164
This function checks to see if an object is circular. If a search key is passed in it will only return `true` if the object is circular and the search key is the key that caused the circularity.
165165

166+
This function will also check all nested objects for circularity.
167+
166168
```js
167169
const objectutils = require("js-object-utilities");
168170

@@ -179,3 +181,24 @@ console.log(isRandomKeyCircular); // false
179181
const isArray2KeyCircular = objectutils.isCircular(object, "array2");
180182
console.log(isArray2KeyCircular); // true
181183
```
184+
185+
### objectutils.circularKeys(obj[, searchKey])
186+
187+
This function is identical to `objectutils.isCircular` except it returns an array of keys that are circular.
188+
189+
```js
190+
const objectutils = require("js-object-utilities");
191+
192+
let object = {};
193+
object.array = {"first": 1};
194+
object.array2 = object;
195+
196+
const circularKeys = objectutils.circularKeys(object);
197+
console.log(circularKeys); // ["array2"]
198+
199+
const randomKeyCircular = objectutils.circularKeys(object, "random");
200+
console.log(randomKeyCircular); // []
201+
202+
const array2KeyCircular = objectutils.circularKeys(object, "array2");
203+
console.log(array2KeyCircular); // ["array2"]
204+
```

lib/circularKeys.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import {GeneralObject} from "./types";
2+
3+
const originalKey = "object";
4+
export = <T>(object: GeneralObject<T>, searchKey?: string): string[] => {
5+
const keys = [];
6+
const stack = [];
7+
const stackSet = new Set();
8+
const detected: string[] = [];
9+
10+
function detect (object: GeneralObject<any>, key: string) {
11+
if (object && typeof object !== "object") {
12+
return;
13+
}
14+
15+
if (stackSet.has(object)) { // it's cyclic! Print the object and its locations.
16+
const oldIndex: number = stack.indexOf(object);
17+
const l1: string = keys.join(".") + "." + key;
18+
// const l2: string = keys.slice(0, oldIndex + 1).join(".");
19+
const currentKey: string = l1.replace(`${originalKey}.`, "");
20+
21+
if (!searchKey || currentKey === searchKey) {
22+
detected.push(currentKey);
23+
return;
24+
} else {
25+
return;
26+
}
27+
}
28+
29+
keys.push(key);
30+
stack.push(object);
31+
stackSet.add(object);
32+
for (const k in object) { //dive on the object's children
33+
if (Object.prototype.hasOwnProperty.call(object, k)) {
34+
detect(object[k], k);
35+
}
36+
}
37+
38+
keys.pop();
39+
stack.pop();
40+
stackSet.delete(object);
41+
return;
42+
}
43+
44+
detect(object, originalKey);
45+
return detected;
46+
};

lib/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import equals = require("./equals");
88
import {clearEmpties} from "./clear_empties";
99
import {GeneralObject, GeneralObjectOrValue} from "./types";
1010
import isCircular = require("./isCircular");
11+
import circularKeys = require("./circularKeys");
1112

1213
export {
1314
get,
@@ -19,6 +20,7 @@ export {
1920
equals,
2021
clearEmpties,
2122
isCircular,
23+
circularKeys,
2224
GeneralObject,
2325
GeneralObjectOrValue
2426
};

lib/isCircular.ts

Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,6 @@
1+
import circularKeys = require("./circularKeys");
12
import {GeneralObject} from "./types";
23

3-
const originalKey = "object";
44
export = <T>(object: GeneralObject<T>, searchKey?: string): boolean => {
5-
const keys = [];
6-
const stack = [];
7-
const stackSet = new Set();
8-
let detected = false;
9-
10-
function detect (object: GeneralObject<any>, key: string) {
11-
if (object && typeof object !== "object") {
12-
return;
13-
}
14-
15-
if (stackSet.has(object)) { // it's cyclic! Print the object and its locations.
16-
const oldIndex: number = stack.indexOf(object);
17-
const l1: string = keys.join(".") + "." + key;
18-
// const l2: string = keys.slice(0, oldIndex + 1).join(".");
19-
20-
if (!searchKey || l1.replace(`${originalKey}.`, "") === searchKey) {
21-
detected = true;
22-
return;
23-
} else {
24-
return;
25-
}
26-
}
27-
28-
keys.push(key);
29-
stack.push(object);
30-
stackSet.add(object);
31-
for (const k in object) { //dive on the object's children
32-
if (Object.prototype.hasOwnProperty.call(object, k)) {
33-
detect(object[k], k);
34-
}
35-
}
36-
37-
keys.pop();
38-
stack.pop();
39-
stackSet.delete(object);
40-
return;
41-
}
42-
43-
detect(object, originalKey);
44-
return detected;
5+
return circularKeys(object, searchKey).length > 0;
456
};

test/circularKeys.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
const {expect} = require("chai");
2+
const utils = require("../dist/index");
3+
4+
describe("utils.circularKeys", () => {
5+
it("Should be a function", () => {
6+
expect(utils.circularKeys).to.be.a("function");
7+
});
8+
9+
const tests = [
10+
{
11+
"input": [{"hello": "world"}],
12+
"output": []
13+
},
14+
{
15+
"input": [{"hello": "world"}, "hello"],
16+
"output": []
17+
},
18+
{
19+
"input": [{"hello": "world"}, "random"],
20+
"output": []
21+
},
22+
{
23+
"input": [{"data": {"hello": "world"}}],
24+
"output": []
25+
},
26+
{
27+
"input": [(() => {
28+
let object = {};
29+
object.array = {"first": 1};
30+
object.array2 = object;
31+
return object;
32+
})()],
33+
"output": ["array2"]
34+
},
35+
{
36+
"input": [(() => {
37+
let object = {};
38+
object.array = {"first": 1};
39+
object.array2 = object;
40+
return object;
41+
})(), "random"],
42+
"output": []
43+
},
44+
{
45+
"input": [(() => {
46+
let object = {};
47+
object.array = {"first": 1};
48+
object.array2 = object;
49+
return object;
50+
})(), "array2"],
51+
"output": ["array2"]
52+
},
53+
{
54+
"input": [{"data": (() => {
55+
let object = {};
56+
object.array = {"first": 1};
57+
object.array2 = object;
58+
return object;
59+
})()}],
60+
"output": ["data.array2"]
61+
},
62+
{
63+
"input": [{"data": (() => {
64+
let object = {};
65+
object.array = {"first": 1};
66+
object.array2 = object;
67+
return object;
68+
})()}, "random"],
69+
"output": []
70+
},
71+
{
72+
"input": [{"data": (() => {
73+
let object = {};
74+
object.array = {"first": 1};
75+
object.array2 = object;
76+
return object;
77+
})()}, "array2"],
78+
"output": []
79+
},
80+
{
81+
"input": [{"data": (() => {
82+
let object = {};
83+
object.array = {"first": 1};
84+
object.array2 = object;
85+
return object;
86+
})()}, "data.array2"],
87+
"output": ["data.array2"]
88+
}
89+
];
90+
91+
tests.forEach((test) => {
92+
it(`Should return ${test.output} for ${test.input}`, () => {
93+
expect(utils.circularKeys(...test.input)).to.eql(test.output);
94+
});
95+
});
96+
});

0 commit comments

Comments
 (0)