diff --git a/packages/nodes-base/nodes/Merge.node.ts b/packages/nodes-base/nodes/Merge.node.ts index 40ee4cf351..d1017e9722 100644 --- a/packages/nodes-base/nodes/Merge.node.ts +++ b/packages/nodes-base/nodes/Merge.node.ts @@ -34,8 +34,13 @@ export class Merge implements INodeType { description: 'Combines data of both inputs. The output will contain items of input 1 and input 2.', }, { - name: 'Merge', - value: 'merge', + name: 'Merge By Index', + value: 'mergeByIndex', + description: 'Merges data of both inputs. The output will contain items of input 1 merged with data of input 2. Merge happens depending on the index of the items. So first item of input 1 will be merged with first item of input 2 and so on.', + }, + { + name: 'Merge By Key', + value: 'mergeByKey', description: 'Merges data of both inputs. The output will contain items of input 1 merged with data of input 2. Merge happens depending on a defined key.', }, { @@ -50,7 +55,38 @@ export class Merge implements INodeType { }, ], default: 'append', - description: 'How data should be merged. If it should simply
be appended or merged depending on a property.', + description: 'How data of branches should be merged.', + }, + { + displayName: 'Join', + name: 'join', + type: 'options', + displayOptions: { + show: { + mode: [ + 'mergeByIndex' + ], + }, + }, + options: [ + { + name: 'Inner Join', + value: 'inner', + description: 'Merges as many items as both inputs contain. (Example: Input1 = 5 items, Input2 = 3 items | Output will contain 3 items)', + }, + { + name: 'Left Join', + value: 'left', + description: 'Merges as many items as first input contains. (Example: Input1 = 3 items, Input2 = 5 items | Output will contain 3 items)', + }, + { + name: 'Outer Join', + value: 'outer', + description: 'Merges as many items as input contains with most items. (Example: Input1 = 3 items, Input2 = 5 items | Output will contain 5 items)', + }, + ], + default: 'left', + description: 'How many items the output will contain
if inputs contain different amount of items.', }, { displayName: 'Property Input 1', @@ -61,7 +97,7 @@ export class Merge implements INodeType { displayOptions: { show: { mode: [ - 'merge' + 'mergeByKey' ], }, }, @@ -76,7 +112,7 @@ export class Merge implements INodeType { displayOptions: { show: { mode: [ - 'merge' + 'mergeByKey' ], }, }, @@ -122,7 +158,88 @@ export class Merge implements INodeType { for (let i = 0; i < 2; i++) { returnData.push.apply(returnData, this.getInputData(i)); } - } else if (mode === 'merge') { + } else if (mode === 'mergeByIndex') { + // Merges data by index + + const join = this.getNodeParameter('join', 0) as string; + + const dataInput1 = this.getInputData(0); + const dataInput2 = this.getInputData(1); + + if (dataInput1 === undefined || dataInput1.length === 0) { + if (['inner', 'left'].includes(join)) { + // When "inner" or "left" join return empty if first + // input does not contain any items + return [returnData]; + } + + // For "outer" return data of second input + return [dataInput2]; + } + + if (dataInput2 === undefined || dataInput2.length === 0) { + if (['left', 'outer'].includes(join)) { + // When "left" or "outer" join return data of first input + return [dataInput1]; + } + + // For "inner" return empty + return [returnData]; + } + + // Default "left" + let numEntries = dataInput1.length; + if (join === 'inner') { + numEntries = Math.min(dataInput1.length, dataInput2.length); + } else if (join === 'outer') { + numEntries = Math.max(dataInput1.length, dataInput2.length); + } + + let newItem: INodeExecutionData; + for (let i = 0; i < numEntries; i++) { + if (i >= dataInput1.length) { + returnData.push(dataInput2[i]); + continue; + } + if (i >= dataInput2.length) { + returnData.push(dataInput1[i]); + continue; + } + + newItem = { + json: {}, + }; + + if (dataInput1[i].binary !== undefined) { + newItem.binary = {}; + // Create a shallow copy of the binary data so that the old + // data references which do not get changed still stay behind + // but the incoming data does not get changed. + Object.assign(newItem.binary, dataInput1[i].binary); + } + + // Create also a shallow copy of the json data + Object.assign(newItem.json, dataInput1[i].json); + + // Copy json data + for (const key of Object.keys(dataInput2[i].json)) { + newItem.json[key] = dataInput2[i].json[key]; + } + + // Copy binary data + if (dataInput2[i].binary !== undefined) { + if (newItem.binary === undefined) { + newItem.binary = {}; + } + + for (const key of Object.keys(dataInput2[i].binary!)) { + newItem.binary[key] = dataInput2[i].binary![key]; + } + } + + returnData.push(newItem); + } + } else if (mode === 'mergeByKey') { // Merges data by key const dataInput1 = this.getInputData(0); if (!dataInput1) {