{"record":{"id":"5ce73c0620412bd8","repo":"n8n-io/n8n","slug":"merge-expected-object-arg","errorCode":null,"errorMessage":"merge(): expected object arg","messagePattern":"merge\\(\\): expected object arg","errorType":"exception","errorClass":"ExpressionExtensionError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/expression-runtime/src/extensions/array-extensions.ts","lineNumber":223,"sourceCode":"\t\t\t\tnewObj[to] = newObj[from];\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n\t\t\t\tdelete newObj[from];\n\t\t\t}\n\t\t});\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-return\n\t\treturn newObj;\n\t});\n}\n\nfunction mergeObjects(value: Record<string, unknown>, extraArgs: unknown[]): unknown {\n\tconst [other] = extraArgs;\n\n\tif (!other) {\n\t\treturn value;\n\t}\n\n\tif (typeof other !== 'object') {\n\t\tthrow new ExpressionExtensionError('merge(): expected object arg');\n\t}\n\n\tconst newObject = { ...value };\n\tfor (const [key, val] of Object.entries(other)) {\n\t\tif (!(key in newObject)) {\n\t\t\tnewObject[key] = val;\n\t\t}\n\t}\n\treturn newObject;\n}\n\nfunction merge(value: unknown[], extraArgs: unknown[][]): unknown {\n\tconst [others] = extraArgs;\n\n\tif (others === undefined) {\n\t\t// If there are no arguments passed, merge all objects within the array\n\t\tconst merged = value.reduce((combined, current) => {\n\t\t\tif (current !== null && typeof current === 'object' && !Array.isArray(current)) {","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/expression-runtime/src/extensions/array-extensions.ts#L205-L241","documentation":"Thrown as ExpressionExtensionError by mergeObjects() when its second argument (the object to merge in) is truthy but not of type 'object'. mergeObjects copies keys from `other` into the target only if absent. This guard is defensive: through the public .merge() API the merge() wrapper pre-filters elements with typeof === 'object' checks in both its no-arg reduce path and its array-arg path, so reaching this throw normally means unexpected internal data or a direct call to mergeObjects with a primitive.","triggerScenarios":"Directly invoking mergeObjects with a primitive second argument (number, string, boolean) that is truthy. Indirectly, only if the merge() wrapper's typeof guards are bypassed by malformed input that slips through — e.g. an element that is a primitive where the wrapper expected an object. In typical {{ [...]().merge(...) }} usage this is hard to hit.","commonSituations":"An internal caller or test invoking mergeObjects directly with a non-object. A future refactor that weakens the merge() wrapper's pre-filtering. Edge data where an object field is unexpectedly a primitive.","solutions":["Ensure any value merged into an object is itself a plain object (not a primitive).","If calling merge() with an argument array, make sure each element is an object: .merge([{id:1,otherValue:3}]).","Filter the array to objects only before merging: .filter(x => x && typeof x === 'object').","If you hit this from normal usage, report it — the merge() wrapper should shield callers from it."],"exampleFix":"// before — a primitive slips into the merge path\nmergeObjects(target, [42]); // 42 is truthy and not an object -> throws\n// after — merge an object\nmergeObjects(target, [{ extra: 1 }]);\n// in an expression, keep elements as objects\n{{ $json.rows.merge([{ id: 1, otherValue: 3 }]) }}","handlingStrategy":"validation","validationCode":"function mergeObjectsSafe(target, other) {\n  if (other && typeof other !== 'object') throw new TypeError('merge(): expected object arg');\n  const out = { ...target }; for (const [k, v] of Object.entries(other)) if (!(k in out)) out[k] = v; return out;\n}","typeGuard":"function isMergeableObject(v) { return v !== null && v !== undefined && typeof v === 'object'; }","tryCatchPattern":"try {\n  result = arr.merge([{ id: 1, otherValue: 3 }]);\n} catch (e) {\n  if (e.name === 'ExpressionExtensionError' && /merge.*object/i.test(e.message)) {\n    // ensure each element being merged is a plain object, not a primitive\n  }\n}","preventionTips":["Ensure every value merged into an object is itself a plain object.","Filter arrays to objects before merging: .filter(x => x && typeof x === 'object').merge(...).","Pass merge arguments as arrays of objects: .merge([{id:1,otherValue:3}]).","Report it if reached through normal .merge() usage — the wrapper should shield callers."],"tags":["expression-runtime","array-extensions","merge","argument-validation","defensive"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}