Skip to content

Commit 54a9ed4

Browse files
support objects with circular references (#9)
1 parent d845e38 commit 54a9ed4

File tree

2 files changed

+100
-4
lines changed

2 files changed

+100
-4
lines changed

src/index.js

+30-4
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,30 @@ function objectValues(obj) {
1010
});
1111
}
1212

13-
function camelCaseRecursive(obj) {
13+
function camelCaseRecursive(obj, cache) {
14+
if (!cache) {
15+
cache = new WeakMap();
16+
}
17+
let res;
18+
if (obj !== null && isObject(obj)) {
19+
if (cache.has(obj)) {
20+
return cache.get(obj);
21+
}
22+
if (Array.isArray(obj)) {
23+
res = [];
24+
} else {
25+
res = {};
26+
}
27+
cache.set(obj, res);
28+
}
1429

1530
const transform = obj == null ? obj : mapObj(obj, (key, val) => {
1631
const newArray = [];
1732

1833
if (Array.isArray(val)) {
1934
val.forEach((value) => {
2035
if (isObject(value) && !Array.isArray(value)) {
21-
newArray.push(camelCaseRecursive(value));
36+
newArray.push(camelCaseRecursive(value, cache));
2237
} else {
2338
newArray.push(value);
2439
}
@@ -30,13 +45,24 @@ function camelCaseRecursive(obj) {
3045
} else if (val instanceof Date) {
3146
return [camelCase(key), val];
3247
} else if (isObject(val)) {
33-
return [camelCase(key), camelCaseRecursive(val)];
48+
return [camelCase(key), camelCaseRecursive(val, cache)];
3449
}
3550

3651
return [camelCase(key), val];
3752
});
3853

39-
return (Array.isArray(obj) ? objectValues(transform) : transform);
54+
if (obj !== null && isObject(obj)) {
55+
// mutate the entry in the cache so that self-references still line up
56+
if (Array.isArray(obj)) {
57+
objectValues(transform).forEach(entry => res.push(entry));
58+
} else {
59+
Object.entries(transform).forEach(([k, v]) => res[k] = v);
60+
}
61+
62+
return res;
63+
}
64+
65+
return transform;
4066

4167
}
4268

test/index.js

+70
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,76 @@ describe('Nested keys within arrays and objects are camelCased', () => {
174174

175175
});
176176

177+
describe('Circular references', () => {
178+
it('Should handle objects with circular references', () => {
179+
const parent = {
180+
my_name: 'parent',
181+
};
182+
parent.child = parent;
183+
184+
const selfReferentialCamel = camelCaseKeys(parent);
185+
186+
expect(Object.keys(selfReferentialCamel)).deep.equals(['myName', 'child']);
187+
expect(Object.keys(selfReferentialCamel.child)).deep.equals(['myName', 'child']);
188+
expect(selfReferentialCamel).to.equal(selfReferentialCamel.child);
189+
});
190+
191+
it('Should handle objects with circular references in their children', () => {
192+
const parent = {
193+
name: 'parent',
194+
};
195+
const child = {
196+
name: 'child',
197+
my_own_parent: parent,
198+
}
199+
parent.child = child;
200+
201+
const selfReferentialCamel = camelCaseKeys(parent);
202+
203+
expect(Object.keys(selfReferentialCamel)).deep.equals(['name', 'child']);
204+
expect(Object.keys(selfReferentialCamel.child)).deep.equals(['name', 'myOwnParent']);
205+
expect(selfReferentialCamel).to.equal(selfReferentialCamel.child.myOwnParent);
206+
});
207+
208+
it('Should handle arrays with objects with circular references in their children', () => {
209+
const parent = {
210+
name: 'parent',
211+
};
212+
const child = {
213+
name: 'child',
214+
my_own_parent: parent,
215+
}
216+
parent.child = child;
217+
218+
const selfReferentialCamel = camelCaseKeys([parent]);
219+
220+
expect(selfReferentialCamel).to.have.lengthOf(1);
221+
expect(Object.keys(selfReferentialCamel[0])).deep.equals(['name', 'child']);
222+
expect(Object.keys(selfReferentialCamel[0].child)).deep.equals(['name', 'myOwnParent']);
223+
expect(selfReferentialCamel[0]).to.equal(selfReferentialCamel[0].child.myOwnParent);
224+
});
225+
226+
it('Should handle objects with circular references in arrays of their children', () => {
227+
const parent = {
228+
name: 'parent',
229+
};
230+
const child = {
231+
name: 'child',
232+
my_own_parent: parent,
233+
}
234+
parent.children = [child, child];
235+
236+
const selfReferentialCamel = camelCaseKeys(parent);
237+
238+
expect(Object.keys(selfReferentialCamel)).deep.equals(['name', 'children']);
239+
expect(Object.keys(selfReferentialCamel.children)).to.have.lengthOf(2);
240+
expect(Object.keys(selfReferentialCamel.children[0])).deep.equals(['name', 'myOwnParent']);
241+
expect(Object.keys(selfReferentialCamel.children[1])).deep.equals(['name', 'myOwnParent']);
242+
expect(selfReferentialCamel).to.equal(selfReferentialCamel.children[0].myOwnParent);
243+
expect(selfReferentialCamel).to.equal(selfReferentialCamel.children[1].myOwnParent);
244+
});
245+
});
246+
177247
describe('Handling null root object', () => {
178248

179249
it('Should not throw Error', () => {

0 commit comments

Comments
 (0)