Partial Deep Helper in TypeScript
This challenge asks you to implement a utility function that deeply merges properties from one or more objects into a target object, but only for properties that are present in a provided partial type. This is useful when you want to update a complex object with only a subset of its properties, ensuring type safety and avoiding unintended modifications. The function should handle nested objects and arrays gracefully.
Problem Description
You need to create a function called partialDeepMerge that takes a target object, one or more source objects, and a partial type as input. The function should deeply merge the properties from the source objects into the target object, but only for properties that are defined in the partial type. If a property exists in the target object and the source object, and the property is present in the partial type, the source object's value should overwrite the target object's value. If a property exists in the source object but not in the partial type, it should be ignored. Deep merging means that if a property is an object or array, the function should recursively merge the properties of that object or array.
Key Requirements:
- Partial Type: The function must respect the provided partial type, only merging properties defined within it.
- Deep Merge: Nested objects and arrays should be merged recursively.
- Overwrite: Source object values should overwrite target object values for matching properties within the partial type.
- Immutability: The target object should not be modified directly. A new object should be returned.
- Multiple Sources: The function should accept multiple source objects to merge from.
Expected Behavior:
The function should return a new object that is a deep merge of the target object and the source objects, limited to the properties defined in the partial type.
Edge Cases to Consider:
- Empty source objects.
- Null or undefined values in source or target objects.
- Circular references (handle gracefully, ideally by avoiding infinite recursion - this is a bonus).
- Arrays: Arrays should be merged by overwriting elements at the same index.
- Primitive types: Primitive types (string, number, boolean) should be directly overwritten.
Examples
Example 1:
Input:
target: { a: 1, b: { c: 2, d: 3 }, e: 4 }
sources: [{ a: 5, b: { c: 6 } }, { b: { d: 7, e: 8 }, f: 9 }]
partial: Partial<typeof target>
Output: { a: 5, b: { c: 6, d: 7 }, e: 4 }
Explanation: 'a' is updated from the first source. 'b.c' is updated from the first source. 'b.d' is updated from the second source. 'e' remains unchanged because it's not in the partial type. 'f' is ignored because it's not in the partial type.
Example 2:
Input:
target: { x: "hello", y: [1, 2, 3] }
sources: [{ x: "world", y: [4, 5] }]
partial: Partial<typeof target>
Output: { x: "world", y: [4, 5] }
Explanation: 'x' is updated. 'y' is overwritten with the new array from the source.
Example 3: (Edge Case - Empty Source)
Input:
target: { p: 10, q: { r: 20 } }
sources: []
partial: Partial<typeof target>
Output: { p: 10, q: { r: 20 } }
Explanation: No sources to merge from, so the target object is returned unchanged.
Constraints
- The target object and source objects can be of any shape, but the partial type must accurately reflect the structure.
- The partial type must be a valid
Partial<typeof target>type. - The function should handle objects with a depth of up to 10 levels of nesting without performance issues.
- The function should be reasonably performant for objects with up to 100 properties.
Notes
- Consider using recursion to handle deep merging.
- Type safety is crucial. Leverage TypeScript's type system to ensure the function behaves as expected.
- Think about how to handle potential errors or unexpected input gracefully.
- The
Partial<typeof target>type is key to restricting the merge to only the specified properties. Make sure you understand how this type works. - A helper function to recursively merge objects might be beneficial.