Skip to content

Commit 8ad5194

Browse files
committed
Clean empty arrays and objects
1 parent af0245d commit 8ad5194

2 files changed

Lines changed: 98 additions & 32 deletions

File tree

ts/server/src/core/extract.spec.ts

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,38 @@ import { FPMLValidationError, resolveTemplate } from './extract';
33
describe('Transformation', () => {
44
const resource = { list: [{ key: 1 }, { key: 2 }, { key: 3 }] } as any;
55

6+
test.skip('null values are not removed', () => {
7+
const resourceWithEmptyArrayNullable = {
8+
root: { resourceType: 'Example', list: [null, { nested: null }, null] },
9+
} as any;
10+
expect(
11+
resolveTemplate(
12+
resourceWithEmptyArrayNullable,
13+
{ root: '{{ %Resource.root }}' },
14+
{ Resource: resourceWithEmptyArrayNullable },
15+
null,
16+
null,
17+
true,
18+
),
19+
).toStrictEqual({ root: { resourceType: 'Example', list: [null, { nested: null }] } });
20+
});
21+
22+
test('undefined value are removed', () => {
23+
const resourceWithEmptyArray = {
24+
root: { resourceType: 'Example', list: [undefined, { nested: undefined }] },
25+
} as any;
26+
expect(
27+
resolveTemplate(
28+
resourceWithEmptyArray,
29+
{ root: '{{ %Resource.root }}' },
30+
{ Resource: resourceWithEmptyArray },
31+
null,
32+
null,
33+
true,
34+
),
35+
).toStrictEqual({ root: { resourceType: 'Example' } });
36+
});
37+
638
test('fails on accessing props of resource in strict mode', () => {
739
expect(() =>
840
resolveTemplate(resource, { key: '{{ list.key }}' }, {}, null, null, true),
@@ -61,12 +93,12 @@ describe('Transformation', () => {
6193
).toStrictEqual({ key: 1 });
6294
});
6395

64-
test('for empty object return empty object', () => {
65-
expect(resolveTemplate(resource, {})).toStrictEqual({});
96+
test('for empty object return undefined', () => {
97+
expect(resolveTemplate(resource, {})).toBeUndefined();
6698
});
6799

68-
test('for empty array return empty array', () => {
69-
expect(resolveTemplate(resource, [])).toStrictEqual([]);
100+
test('for empty array return undefined', () => {
101+
expect(resolveTemplate(resource, [])).toBeUndefined();
70102
});
71103

72104
test('for array of arrays returns flattened array', () => {
@@ -92,8 +124,12 @@ describe('Transformation', () => {
92124
expect(resolveTemplate(resource, { key: null })).toStrictEqual({ key: null });
93125
});
94126

95-
test('for object with undefined keys returns undefined keys', () => {
96-
expect(resolveTemplate(resource, { key: undefined })).toStrictEqual({ key: undefined });
127+
test('for object with undefined keys returns undefined', () => {
128+
expect(resolveTemplate(resource, { key: undefined })).toBeUndefined();
129+
});
130+
131+
test('for object with undefined keys returns only defined keys', () => {
132+
expect(resolveTemplate(resource, { key: undefined, key2: 1 })).toStrictEqual({ key2: 1 });
97133
});
98134

99135
test('for object with non-null keys returns non-null keys', () => {
@@ -230,13 +266,18 @@ describe('Assign block', () => {
230266

231267
test('works with undefined intermediate values', () => {
232268
expect(
233-
resolveTemplate(resource, {
234-
'{% assign %}': [{ varA: '{{ {} }}' }, { varB: '{{ %varA }}' }],
235-
valueA: '{{ %varB }}',
236-
}),
237-
).toStrictEqual({
238-
valueA: undefined,
239-
});
269+
resolveTemplate(
270+
resource,
271+
{
272+
'{% assign %}': [{ varA: '{{ {} }}' }, { varB: '{{ %varA }}' }],
273+
valueA: '{{ %varB }}',
274+
},
275+
null,
276+
null,
277+
null,
278+
true,
279+
),
280+
).toBeUndefined();
240281
});
241282

242283
test('works with multiple vars as array of objects', () => {
@@ -478,9 +519,7 @@ describe('If block', () => {
478519
"{% if key != 'value' %}": { nested: "{{ 'true' + key }}" },
479520
},
480521
}),
481-
).toStrictEqual({
482-
result: undefined,
483-
});
522+
).toBeUndefined();
484523
});
485524

486525
test('returns null for falsy condition with nullable else branch', () => {
@@ -600,7 +639,7 @@ describe('If block', () => {
600639
resolveTemplate(resource, {
601640
result: {
602641
myKey: 1,
603-
"{% if key = 'value' %}": [],
642+
"{% if key = 'value' %}": [{ key1: true }],
604643
},
605644
}),
606645
).toThrow(FPMLValidationError);
@@ -612,7 +651,7 @@ describe('If block', () => {
612651
result: {
613652
myKey: 1,
614653
"{% if key != 'value' %}": {},
615-
'{% else %}': [],
654+
'{% else %}': [{ key1: true }],
616655
},
617656
}),
618657
).toThrow(FPMLValidationError);

ts/server/src/core/extract.ts

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ function resolveTemplateRecur(
106106

107107
return { node, context };
108108
},
109-
)[rootNodeKey];
109+
)?.[rootNodeKey];
110110
}
111111

112112
function processTemplateString(
@@ -164,7 +164,6 @@ function processAssignBlock(
164164
): { node: any; context: Context } {
165165
const extendedContext = { ...context };
166166
const keys = Object.keys(node);
167-
168167
const assignRegExp = /{%\s*assign\s*%}/;
169168
const assignKey = keys.find((k) => k.match(assignRegExp));
170169
if (assignKey) {
@@ -177,8 +176,17 @@ function processAssignBlock(
177176
);
178177
}
179178

180-
Object.entries(
181-
resolveTemplateRecur(path, resource, obj, extendedContext, model, fpOptions),
179+
return Object.entries(
180+
mapValues(obj, (objValue) =>
181+
resolveTemplateRecur(
182+
path,
183+
resource,
184+
objValue,
185+
extendedContext,
186+
model,
187+
fpOptions,
188+
),
189+
),
182190
).forEach(([key, value]) => {
183191
extendedContext[key] = value;
184192
});
@@ -191,13 +199,15 @@ function processAssignBlock(
191199
);
192200
}
193201
Object.entries(
194-
resolveTemplateRecur(
195-
path,
196-
resource,
197-
node[assignKey],
198-
extendedContext,
199-
model,
200-
fpOptions,
202+
mapValues(node[assignKey], (objValue) =>
203+
resolveTemplateRecur(
204+
path,
205+
resource,
206+
objValue,
207+
extendedContext,
208+
model,
209+
fpOptions,
210+
),
201211
),
202212
).forEach(([key, value]) => {
203213
extendedContext[key] = value;
@@ -398,19 +408,36 @@ type Transformer = (path: Path, node: any, context: Context) => { node: any; con
398408
function iterateObject(startPath: Path, obj: any, context: Context, transform: Transformer): any {
399409
if (Array.isArray(obj)) {
400410
// Arrays are flattened and null/undefined values are removed here
401-
return obj
411+
const cleanedArray = obj
402412
.flatMap((value, index) => {
403413
const result = transform([...startPath, index], value, context);
404414

405-
return iterateObject([...startPath, index], result.node, result.context, transform);
415+
const testResult = iterateObject(
416+
[...startPath, index],
417+
result.node,
418+
result.context,
419+
transform,
420+
);
421+
console.log(testResult);
422+
return testResult;
406423
})
407424
.filter((x) => x !== null && x !== undefined);
425+
return cleanedArray.length ? cleanedArray : undefined;
408426
} else if (isPlainObject(obj)) {
409-
return mapValues(obj, (value, key) => {
427+
const objResult = mapValues(obj, (value, key) => {
410428
const result = transform([...startPath, key], value, context);
411429

412430
return iterateObject([...startPath, key], result.node, result.context, transform);
413431
});
432+
433+
if (isPlainObject(objResult)) {
434+
const cleanedObject = Object.entries(objResult).filter(([, value]) => value !== undefined);
435+
if (!cleanedObject.length) {
436+
return undefined;
437+
}
438+
return Object.fromEntries(cleanedObject);
439+
}
440+
return objResult;
414441
}
415442

416443
return transform(startPath, obj, context).node;

0 commit comments

Comments
 (0)