diff --git a/packages/jspsych/src/modules/utils.ts b/packages/jspsych/src/modules/utils.ts index 19dbdb20..15e6efd1 100644 --- a/packages/jspsych/src/modules/utils.ts +++ b/packages/jspsych/src/modules/utils.ts @@ -28,3 +28,34 @@ export function deepCopy(obj) { return obj; } } + +/** + * Merges two objects, recursively. + * @param obj1 Object to merge + * @param obj2 Object to merge + */ +export function deepMerge(obj1: any, obj2: any): any { + let merged = {}; + for (const key in obj1) { + if (obj1.hasOwnProperty(key)) { + if (typeof obj1[key] === "object" && obj2.hasOwnProperty(key)) { + merged[key] = deepMerge(obj1[key], obj2[key]); + } else { + merged[key] = obj1[key]; + } + } + } + for (const key in obj2) { + if (obj2.hasOwnProperty(key)) { + if (!merged.hasOwnProperty(key)) { + merged[key] = obj2[key]; + } else if (typeof obj2[key] === "object") { + merged[key] = deepMerge(merged[key], obj2[key]); + } else { + merged[key] = obj2[key]; + } + } + } + + return merged; +} diff --git a/packages/jspsych/tests/utils/utils.test.ts b/packages/jspsych/tests/utils/utils.test.ts index dac8bd36..e1227f96 100644 --- a/packages/jspsych/tests/utils/utils.test.ts +++ b/packages/jspsych/tests/utils/utils.test.ts @@ -1,4 +1,4 @@ -import { deepCopy, unique } from "../../src/modules/utils"; +import { deepCopy, deepMerge, unique } from "../../src/modules/utils"; describe("unique", () => { test("generates unique array when there are duplicates", () => { @@ -44,3 +44,53 @@ describe("deepCopy", () => { expect(o2.b()).toBe(1); }); }); + +describe("deepMerge", () => { + it("should merge two objects with nested properties", () => { + const obj1 = { a: 1, b: { c: { d: 1 } } }; + const obj2 = { b: { c: { e: 2 } }, f: 3 }; + const expected = { a: 1, b: { c: { d: 1, e: 2 } }, f: 3 }; + const result = deepMerge(obj1, obj2); + expect(result).toEqual(expected); + }); + + it("should overwrite properties in obj1 with properties in obj2", () => { + const obj1 = { a: 1, b: { c: { d: 1 } } }; + const obj2 = { a: 2, b: { c: { d: 2 } } }; + const expected = { a: 2, b: { c: { d: 2 } } }; + const result = deepMerge(obj1, obj2); + expect(result).toEqual(expected); + }); + + it("should handle null and undefined values", () => { + const obj1 = { a: null, b: { c: undefined } }; + const obj2 = { a: 1, b: { c: { d: 1 } } }; + const expected = { a: 1, b: { c: { d: 1 } } }; + const result = deepMerge(obj1, obj2); + expect(result).toEqual(expected); + }); + + it("should handle empty objects", () => { + const obj1 = { a: 1, b: {} }; + const obj2 = { b: { c: 2 } }; + const expected = { a: 1, b: { c: 2 } }; + const result = deepMerge(obj1, obj2); + expect(result).toEqual(expected); + }); + + it("should handle when one property is an object and the corresponding property is not", () => { + const obj1 = { a: 1, b: { c: { d: 1 } } }; + const obj2 = { a: 2, b: 3 }; + const expected = { a: 2, b: 3 }; + const result = deepMerge(obj1, obj2); + expect(result).toEqual(expected); + }); + + it("should handle when one property is an object and the corresponding property is not, reversed", () => { + const obj1 = { a: 1, b: { c: { d: 1 } } }; + const obj2 = { a: 2, b: 3 }; + const expected = { a: 1, b: { c: { d: 1 } } }; + const result = deepMerge(obj2, obj1); + expect(result).toEqual(expected); + }); +});