/** * Merges two objects (`base` and `patch`) by overriding values in `base` with non-empty values from `patch`. * * - If a value in `patch` is `null`, `undefined`, or an empty string (`""`), the corresponding value from `base` is used. * - For nested objects, the function performs a recursive merge. * * This function is useful for scenarios where default values (`base`) need to be preserved unless explicitly replaced * by valid values from another source (`patch`). * * @template T - The type of the objects being merged. Defaults to `object`. * @param {T} base - The base object containing default values. * @param {T} patch - The object containing values to override those in `base`. * @returns {T} - A new object that combines properties from `base` and `patch`. * * @example * // Simple merge * const base = { name: "John", age: 30 }; * const patch = { name: "", age: 25 }; * const result = patchObject(base, patch); * console.log(result); // { name: "John", age: 25 } * * @example * // Nested merge * const baseNested = { name: "John", details: { city: "New York", phone: "12345" } }; * const patchNested = { name: "", details: { city: "Los Angeles" } }; * const resultNested = patchObject(baseNested, patchNested); * console.log(resultNested); * // Output: * // { name: "John", details: { city: "Los Angeles", phone: "12345" } } */ export function patchObject>( base: T = {} as T, patch: T = {} as T, ): T { const result = { ...base }; Object.keys(patch).forEach((key) => { const patchValue = patch[key as keyof T]; const baseValue = base[key as keyof T]; if ( patchValue === null || patchValue === '' || patchValue === undefined ) { result[key as keyof T] = baseValue; } else if ( typeof patchValue === 'object' && !Array.isArray(patchValue) ) { result[key as keyof T] = patchObject( (baseValue || {}) as Record, patchValue as Record, ) as T[keyof T]; } else { result[key as keyof T] = patchValue; } }); return result; }