Skip to content

Commit 153cc8b

Browse files
authored
fix(import-export): fix import deep json overwriting variables COMPASS-5971 (#3401)
1 parent 1bb66d0 commit 153cc8b

File tree

4 files changed

+367
-33
lines changed

4 files changed

+367
-33
lines changed

packages/compass-import-export/src/utils/dotnotation.spec.js

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,177 @@ describe('dotnotation', function () {
9090
foo: ['a', 'b'],
9191
});
9292
});
93+
94+
it('should parse deeply nested objects without including objects', function () {
95+
const serializedObject = dotnotation.serialize(
96+
{
97+
supermarket: {
98+
fruits: {
99+
oranges: {
100+
amount: {
101+
'2022-01-15': 1.66,
102+
'2022-02-16': 1.22,
103+
'2022-03-13': 1.11,
104+
'2022-04-14': 7.69,
105+
},
106+
},
107+
apples: {
108+
a: 123,
109+
amount: {
110+
'2022-01-15': 3.47,
111+
'2022-02-14': 4.18,
112+
'2022-03-15': 4.18,
113+
},
114+
},
115+
currency: 'usd',
116+
},
117+
},
118+
test: '123',
119+
},
120+
{ includeObjects: false }
121+
);
122+
123+
expect(serializedObject).to.deep.equal({
124+
'supermarket.fruits.oranges.amount.2022-01-15': 1.66,
125+
'supermarket.fruits.oranges.amount.2022-02-16': 1.22,
126+
'supermarket.fruits.oranges.amount.2022-03-13': 1.11,
127+
'supermarket.fruits.oranges.amount.2022-04-14': 7.69,
128+
'supermarket.fruits.apples.amount.2022-01-15': 3.47,
129+
'supermarket.fruits.apples.amount.2022-02-14': 4.18,
130+
'supermarket.fruits.apples.amount.2022-03-15': 4.18,
131+
'supermarket.fruits.apples.a': 123,
132+
'supermarket.fruits.currency': 'usd',
133+
test: '123',
134+
});
135+
});
136+
137+
it('should parse deeply nested objects without overriding arrays', function () {
138+
const serializedObject = dotnotation.serialize(
139+
{
140+
supermarket: {
141+
17: 76,
142+
fruits: {
143+
apples: {
144+
12: '34',
145+
amount: {
146+
'2022-01-15': 3.47,
147+
'2022-02-14': 4.18,
148+
'2022-03-15': 4.18,
149+
},
150+
a: 123,
151+
},
152+
currency: 'usd',
153+
},
154+
},
155+
test: '123',
156+
a: {
157+
b: {
158+
c: {
159+
17: 76,
160+
d: {
161+
a: 'ok',
162+
99: 'test',
163+
},
164+
f: [
165+
{
166+
aa: {
167+
bb: {
168+
123: 'test',
169+
},
170+
4: 5,
171+
},
172+
},
173+
],
174+
},
175+
},
176+
},
177+
},
178+
{ includeObjects: true }
179+
);
180+
181+
expect(serializedObject).to.deep.equal({
182+
supermarket: {},
183+
'supermarket.17': 76,
184+
'supermarket.fruits': {},
185+
'supermarket.fruits.apples': {},
186+
'supermarket.fruits.apples.12': '34',
187+
'supermarket.fruits.apples.amount': {},
188+
'supermarket.fruits.apples.amount.2022-01-15': 3.47,
189+
'supermarket.fruits.apples.amount.2022-02-14': 4.18,
190+
'supermarket.fruits.apples.amount.2022-03-15': 4.18,
191+
'supermarket.fruits.apples.a': 123,
192+
'supermarket.fruits.currency': 'usd',
193+
test: '123',
194+
a: {},
195+
'a.b': {},
196+
'a.b.c': {},
197+
'a.b.c.17': 76,
198+
'a.b.c.d': {},
199+
'a.b.c.d.a': 'ok',
200+
'a.b.c.d.99': 'test',
201+
'a.b.c.f': [
202+
{
203+
aa: {
204+
4: 5,
205+
bb: {
206+
123: 'test',
207+
},
208+
},
209+
},
210+
],
211+
});
212+
});
213+
214+
it('should parse deeply nested objects', function () {
215+
const serializedObject = dotnotation.serialize(
216+
{
217+
supermarket: {
218+
fruits: {
219+
oranges: {
220+
aTest: ['test'],
221+
amount: {
222+
'2022-01-15': 1.66,
223+
'2022-02-16': 1.22,
224+
'2022-03-13': 1.11,
225+
'2022-04-14': 7.69,
226+
},
227+
},
228+
apples: {
229+
a: 123,
230+
amount: {
231+
'2022-01-15': 3.47,
232+
'2022-02-14': 4.18,
233+
'2022-03-15': 4.18,
234+
},
235+
arrayTest: ['test'],
236+
},
237+
currency: 'usd',
238+
},
239+
},
240+
test: '123',
241+
},
242+
{ includeObjects: true }
243+
);
244+
245+
expect(serializedObject).to.deep.equal({
246+
supermarket: {},
247+
'supermarket.fruits': {},
248+
'supermarket.fruits.oranges': {},
249+
'supermarket.fruits.oranges.aTest': ['test'],
250+
'supermarket.fruits.oranges.amount': {},
251+
'supermarket.fruits.oranges.amount.2022-01-15': 1.66,
252+
'supermarket.fruits.oranges.amount.2022-02-16': 1.22,
253+
'supermarket.fruits.oranges.amount.2022-03-13': 1.11,
254+
'supermarket.fruits.oranges.amount.2022-04-14': 7.69,
255+
'supermarket.fruits.apples': {},
256+
'supermarket.fruits.apples.amount': {},
257+
'supermarket.fruits.apples.amount.2022-01-15': 3.47,
258+
'supermarket.fruits.apples.amount.2022-02-14': 4.18,
259+
'supermarket.fruits.apples.amount.2022-03-15': 4.18,
260+
'supermarket.fruits.apples.arrayTest': ['test'],
261+
'supermarket.fruits.apples.a': 123,
262+
'supermarket.fruits.currency': 'usd',
263+
test: '123',
264+
});
265+
});
93266
});

packages/compass-import-export/src/utils/dotnotation.ts

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -45,43 +45,60 @@ export function serialize(
4545
},
4646
});
4747

48-
if (includeObjects) {
49-
/*
50-
Make sure that paths to objects exist in the returned value before the paths
51-
to properties inside those objects.
52-
ie. for { foo: { 1: 'one', two: 'two' } } we will return
53-
{ foo: {}, 'foo.1': 'one', 'foo.two': 'two' } rather than
54-
{ 'foo.1': 'one', 'foo.two': 'two'}.
55-
56-
This way when we walk the return value later by the time we encounter
57-
'foo.1' we already created foo, initialised to {}. Then _.set(result,
58-
'foo.1', 'one') will not create foo as an array because 1 looks like an
59-
index. This is because at that point result will already contain { foo: {} }
60-
61-
The use-case for this came about because paths that end with numbers are
62-
ambiguous and _.set() will assume it is an array index by default. By
63-
ensuring that there is already an object at the target the ambiguity is
64-
removed.
65-
*/
66-
const withObjects: Record<string, unknown> = {};
67-
const knownParents: Record<string, true> = {};
68-
for (const [path, value] of Object.entries(flattened)) {
69-
const parentPath = path.includes('.')
70-
? path.slice(0, path.lastIndexOf('.'))
71-
: null;
72-
if (parentPath && !knownParents[parentPath]) {
48+
if (!includeObjects) {
49+
return flattened;
50+
}
51+
52+
/*
53+
Make sure that paths to objects exist in the returned value before the paths
54+
to properties inside those objects.
55+
ie. for { foo: { 1: 'one', two: 'two' } } we will return
56+
{ foo: {}, 'foo.1': 'one', 'foo.two': 'two' } rather than
57+
{ 'foo.1': 'one', 'foo.two': 'two'}.
58+
59+
This way when we walk the return value later by the time we encounter
60+
'foo.1' we already created foo, initialized to {}. Then _.set(result,
61+
'foo.1', 'one') will not create foo as an array because 1 looks like an
62+
index. This is because at that point result will already contain { foo: {} }
63+
64+
The use-case for this came about because paths that end with numbers are
65+
ambiguous and _.set() will assume it is an array index by default. By
66+
ensuring that there is already an object at the target the ambiguity is
67+
removed.
68+
*/
69+
const withObjects: Record<string, unknown> = {};
70+
const knownParents: Record<string, true> = {};
71+
72+
for (const [path, value] of Object.entries(flattened)) {
73+
let currentIndex = path.indexOf('.');
74+
75+
let parentPath: string | null =
76+
currentIndex > -1 ? path.slice(0, currentIndex) : null;
77+
78+
// Build all of the parent objects that contain the current path.
79+
// (a.b.c -> a = {} a.b = {})
80+
while (parentPath) {
81+
// Leave arrays alone because they already got handled by safe: true above.
82+
if (!knownParents[parentPath] && !Array.isArray(_.get(obj, parentPath))) {
7383
knownParents[parentPath] = true;
74-
// Leave arrays alone because they already got handled by safe: true above.
75-
if (!Array.isArray(_.get(obj, parentPath))) {
76-
withObjects[parentPath] = {};
77-
}
84+
85+
withObjects[parentPath] = {};
86+
}
87+
88+
currentIndex = path.indexOf('.', currentIndex + 1);
89+
if (currentIndex === -1) {
90+
// No more parents.
91+
break;
7892
}
79-
withObjects[path] = value;
93+
94+
// Continue to the next parent if there is one.
95+
parentPath = path.slice(0, currentIndex);
8096
}
81-
return withObjects;
97+
98+
withObjects[path] = value;
8299
}
83100

84-
return flattened;
101+
return withObjects;
85102
}
86103

87104
/**

packages/compass-import-export/src/utils/import-apply-types-and-projection.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ function transformProjectedTypes(
7474
_.set(result, keyPath, value);
7575
return;
7676
}
77+
7778
const sourceType = getTypeDescriptorForValue(value).type;
7879

7980
let casted = value;
@@ -87,7 +88,7 @@ function transformProjectedTypes(
8788
// sourceType,
8889
// value,
8990
// keyPath,
90-
// casted
91+
// casted,
9192
// });
9293
}
9394

0 commit comments

Comments
 (0)