Skip to content

Commit

Permalink
extend merge built-in function (#3858)
Browse files Browse the repository at this point in the history
  • Loading branch information
Danieladu authored Jul 9, 2021
1 parent c0897ee commit 7afa288
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 28 deletions.
65 changes: 40 additions & 25 deletions libraries/adaptive-expressions/src/builtinFunctions/merge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,50 +6,65 @@
* Licensed under the MIT License.
*/

import { Expression } from '../expression';
import { EvaluateExpressionDelegate, ExpressionEvaluator } from '../expressionEvaluator';
import { EvaluateExpressionDelegate, ExpressionEvaluator, ValueWithError } from '../expressionEvaluator';
import { ExpressionType } from '../expressionType';
import { FunctionUtils } from '../functionUtils';
import { ReturnType } from '../returnType';

/**
* Merge two JSON objects into one JSON object.
* Merge multiple object(json) into one object(json).
* If the item is array, the elements of the array are merged as well.
*/
export class Merge extends ExpressionEvaluator {
/**
* Initializes a new instance of the [Merge](xref:adaptive-expressions.Merge) class.
*/
public constructor() {
super(ExpressionType.Merge, Merge.evaluator(), ReturnType.Object, Merge.validator);
super(ExpressionType.Merge, Merge.evaluator(), ReturnType.Object, FunctionUtils.validateAtLeastOne);
}

/**
* @private
*/
private static evaluator(): EvaluateExpressionDelegate {
return FunctionUtils.applySequenceWithError((args: any[]): any => {
let value: any;
let error: string;
if (
typeof args[0] === 'object' &&
!Array.isArray(args[0]) &&
typeof args[1] === 'object' &&
!Array.isArray(args[1])
) {
Object.assign(args[0], args[1]);
value = args[0];
} else {
error = `The argumets ${args[0]} and ${args[1]} must be JSON objects.`;
}
return FunctionUtils.applyWithError(
(args: unknown[]): ValueWithError => {
const result = {};
for (const arg of args) {
const objectResult = this.parseToObjectList(arg);
if (objectResult.error != null) {
return { value: undefined, error: objectResult.error };
}

for (const item of objectResult.result) {
Object.assign(result, item);
}
}

return { value, error };
});
return { value: result, error: undefined };
}
);
}

/**
* @private
*/
private static validator(expression: Expression): void {
FunctionUtils.validateArityAndAnyType(expression, 2, Number.MAX_SAFE_INTEGER);
private static parseToObjectList(arg: unknown): { result: Record<string, unknown>[]; error: string } {
const result: Record<string, unknown>[] = [];
let error: string;
if (arg == null) {
error = `The argument ${arg} must be a JSON object or array.`;
} else if (Array.isArray(arg)) {
for (const item of arg) {
if (typeof item === 'object' && !Array.isArray(item)) {
result.push(item);
} else {
error = `The argument ${item} in array must be a JSON object.`;
}
}
} else if (typeof arg === 'object') {
result.push(arg as Record<string, unknown>);
} else {
error = `The argument ${arg} must be a JSON object or array.`;
}

return { result: result, error: error };
}
}
5 changes: 2 additions & 3 deletions libraries/adaptive-expressions/tests/badExpression.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -470,9 +470,8 @@ const badExpressions = [
['jPath(hello)', 'should have two params'],
['jPath(hello, \'.key\')', 'bad json'],
['jPath(json(\'{"key1":"value1","key2":"value2"}\'), \'getTotal\')', 'bad path'],
['merge(json(\'{"key1":"value1","key2":"value2"}\'))', 'should have at least 2 arguments'],
['merge(json2, jarray1)', 'should only have JSON object arguments'],
['merge(jarray1, json2)', 'should only have JSON object arguments'],
['merge(1, jarray1)', 'should only have JSON object or array arguments'],
['merge([jarray1])', 'not support nested array'],
['xml("invalid json string")'],
//['xPath(invalidXml, "sum(/produce/item/count)")'], currently, this test did not throw error correctly
['xPath(invalidXml)'],
Expand Down
29 changes: 29 additions & 0 deletions libraries/adaptive-expressions/tests/expressionParser.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,9 @@ const testCases = [
['jPath(jsonStr, \'.automobiles[0].maker\' )', ['Nissan']],
['string(merge(json1, json2))', '{"FirstName":"John","LastName":"Smith","Enabled":true,"Roles":["Customer","Admin"]}'],
['string(merge(json1, json2, json3))', '{"FirstName":"John","LastName":"Smith","Enabled":true,"Roles":["Customer","Admin"],"age":36}'],
['merge(callstack[1], callstack[2]).z', 1],
['merge(callstack).z', 1],
['string(merge({k1:\'v1\'}, [{k2:\'v2\'}, {k3: \'v3\'}], {k4:\'v4\'}))', '{"k1":"v1","k2":"v2","k3":"v3","k4":"v4"}'],
[
'xml(\'{"person": {"name": "Sophia Owen", "city": "Seattle"}}\')',
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<person>\n <name>Sophia Owen</name>\n <city>Seattle</city>\n</person>',
Expand Down Expand Up @@ -1009,6 +1012,32 @@ const scope = {
doubleNestedItems: [[{ x: 1 }, { x: 2 }], [{ x: 3 }]],
xmlStr:
"<?xml version='1.0'?> <produce> <item> <name>Gala</name> <type>apple</type> <count>20</count> </item> <item> <name>Honeycrisp</name> <type>apple</type> <count>10</count> </item> </produce>",
callStack: [
{
x: 3,
instance: {
xxx: 'instance',
yyy: {
instanceY: 'instanceY',
},
},
options: {
xxx: 'options',
yyy: ['optionY1', 'optionY2'],
},
title: 'Dialog Title',
subTitle: 'Dialog Sub Title',
},
{
x: 2,
y: 2,
},
{
x: 1,
y: 1,
z: 1,
},
],
};

const generateParseTest = (input, expectedOutput, expectedRefs) => () => {
Expand Down

0 comments on commit 7afa288

Please sign in to comment.