Helper functions and enums for object type detection, property manipulation, and merge operation support.
Utility function to determine the type of an object for merge strategy selection.
/**
* Get the type of the given object.
* @param object - The object to get the type of.
* @returns The type of the given object.
*/
function getObjectType(object: unknown): ObjectType;
/**
* The different types of objects deepmerge-ts supports.
* Exported as a type (const enum values).
*/
type ObjectType = 0 | 1 | 2 | 3 | 4 | 5;
// Const enum values for reference:
// NOT = 0 - Not an object (null, undefined, primitives)
// RECORD = 1 - Plain object/record
// ARRAY = 2 - Array
// SET = 3 - Set
// MAP = 4 - Map
// OTHER = 5 - Other object types (functions, classes, etc.)Usage Examples:
import { getObjectType } from "deepmerge-ts";
// Primitive types
console.log(getObjectType(42)); // 0 (NOT)
console.log(getObjectType("hello")); // 0 (NOT)
console.log(getObjectType(null)); // 0 (NOT)
console.log(getObjectType(undefined)); // 0 (NOT)
// Object types
console.log(getObjectType({})); // 1 (RECORD)
console.log(getObjectType({ a: 1 })); // 1 (RECORD)
console.log(getObjectType([])); // 2 (ARRAY)
console.log(getObjectType([1, 2, 3])); // 2 (ARRAY)
console.log(getObjectType(new Set())); // 3 (SET)
console.log(getObjectType(new Map())); // 4 (MAP)
// Other types
console.log(getObjectType(() => {})); // 5 (OTHER)
console.log(getObjectType(new Date())); // 5 (OTHER)
console.log(getObjectType(/regex/)); // 5 (OTHER)
// Custom usage in merge logic (using const enum values)
const ObjectType = {
NOT: 0,
RECORD: 1,
ARRAY: 2,
SET: 3,
MAP: 4,
OTHER: 5
} as const;
function customMergeStrategy(obj1: unknown, obj2: unknown) {
const type1 = getObjectType(obj1);
const type2 = getObjectType(obj2);
if (type1 === ObjectType.ARRAY && type2 === ObjectType.ARRAY) {
return [...(obj1 as unknown[]), ...(obj2 as unknown[])];
}
if (type1 === ObjectType.RECORD && type2 === ObjectType.RECORD) {
return { ...(obj1 as object), ...(obj2 as object) };
}
return obj2; // Use second value for other types
}Utility function for extracting all enumerable property keys, including symbols.
/**
* Get the keys of the given objects including symbol keys.
* Note: Only keys to enumerable properties are returned.
* @param objects - An array of objects to get the keys of.
* @returns A set containing all the keys of all the given objects.
*/
function getKeys(objects: ReadonlyArray<object>): Set<PropertyKey>;Usage Examples:
import { getKeys } from "deepmerge-ts";
// Basic usage
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const keys = getKeys([obj1, obj2]);
console.log(Array.from(keys)); // ["a", "b", "c"]
// With symbol keys
const symbolKey = Symbol("special");
const obj3 = { name: "test", [symbolKey]: "value" };
const obj4 = { name: "other", id: 123 };
const allKeys = getKeys([obj3, obj4]);
console.log(Array.from(allKeys)); // ["name", Symbol(special), "id"]
// Empty objects
const emptyKeys = getKeys([{}, {}]);
console.log(Array.from(emptyKeys)); // []
// Mixed object types
const mixed = [
{ a: 1 },
{ b: 2, [Symbol.iterator]: function* () {} },
{ c: 3 }
];
const mixedKeys = getKeys(mixed);
// Returns all enumerable keys from all objects
// Practical usage in custom merge function
function customRecordMerger(records: object[]) {
const result = {};
const allKeys = getKeys(records);
for (const key of allKeys) {
const values = records
.filter(record => key in record)
.map(record => record[key as keyof typeof record]);
if (values.length > 0) {
result[key as keyof typeof result] = values[values.length - 1];
}
}
return result;
}Utility function to check if an object has a specific enumerable property.
/**
* Does the given object have the given property.
* @param object - The object to test.
* @param property - The property to test.
* @returns Whether the object has the property.
*/
function objectHasProperty(
object: object,
property: PropertyKey,
): boolean;Usage Examples:
import { objectHasProperty } from "deepmerge-ts";
// Basic usage
const obj = { name: "Alice", age: 30 };
console.log(objectHasProperty(obj, "name")); // true
console.log(objectHasProperty(obj, "height")); // false
// Symbol properties
const symbolKey = Symbol("id");
const objWithSymbol = { [symbolKey]: 123, visible: true };
console.log(objectHasProperty(objWithSymbol, symbolKey)); // true
console.log(objectHasProperty(objWithSymbol, "visible")); // true
// Non-enumerable properties are not detected
const objWithNonEnum = {};
Object.defineProperty(objWithNonEnum, "hidden", {
value: "secret",
enumerable: false
});
console.log(objectHasProperty(objWithNonEnum, "hidden")); // false
// Arrays
const arr = ["a", "b", "c"];
console.log(objectHasProperty(arr, "0")); // true
console.log(objectHasProperty(arr, 0)); // true (numeric keys work)
console.log(objectHasProperty(arr, "length")); // false (length is not enumerable)
// Practical usage in merge operations
function safePropertyMerge(target: object, source: object, key: PropertyKey) {
if (objectHasProperty(source, key)) {
const sourceValue = source[key as keyof typeof source];
if (objectHasProperty(target, key)) {
// Both objects have the property - merge them
const targetValue = target[key as keyof typeof target];
return mergeValues(targetValue, sourceValue);
} else {
// Only source has the property - copy it
return sourceValue;
}
}
// Source doesn't have the property - keep target value
return target[key as keyof typeof target];
}
// Filter objects by property existence
function filterObjectsByProperty(objects: object[], property: PropertyKey) {
return objects.filter(obj => objectHasProperty(obj, property));
}
const data = [
{ name: "Alice", email: "alice@example.com" },
{ name: "Bob" },
{ name: "Charlie", email: "charlie@example.com", phone: "123-456-7890" }
];
const withEmail = filterObjectsByProperty(data, "email");
console.log(withEmail.length); // 2
const withPhone = filterObjectsByProperty(data, "phone");
console.log(withPhone.length); // 1Additional utility types and functions for advanced usage scenarios.
/**
* Get an iterable object that iterates over the given iterables.
* @param iterables - Array of iterables to chain together
* @returns Combined iterable that yields values from all input iterables
*/
function getIterableOfIterables<T>(
iterables: ReadonlyArray<Readonly<Iterable<T>>>,
): Iterable<T>;Usage Examples:
import { getIterableOfIterables } from "deepmerge-ts";
// Combine multiple iterables
const arrays = [[1, 2], [3, 4], [5, 6]];
const combined = getIterableOfIterables(arrays);
for (const value of combined) {
console.log(value); // 1, 2, 3, 4, 5, 6
}
// Convert to array
const result = Array.from(combined); // [1, 2, 3, 4, 5, 6]
// Works with different iterable types
const sets = [new Set([1, 2]), new Set([3, 4])];
const maps = [new Map([["a", 1]]), new Map([["b", 2]])];
const strings = ["hello", "world"];
const combinedSets = Array.from(getIterableOfIterables(sets)); // [1, 2, 3, 4]
const combinedMaps = Array.from(getIterableOfIterables(maps)); // [["a", 1], ["b", 2]]
const combinedStrings = Array.from(getIterableOfIterables(strings)); // ["h", "e", "l", "l", "o", "w", "o", "r", "l", "d"]
// Practical usage in custom merge functions
function mergeIterables<T>(iterables: ReadonlyArray<Iterable<T>>): T[] {
return Array.from(getIterableOfIterables(iterables));
}
// Custom Set merge using the utility
function customSetMerge<T>(sets: ReadonlyArray<ReadonlySet<T>>): Set<T> {
const combined = getIterableOfIterables(sets);
return new Set(combined);
}
const set1 = new Set([1, 2, 3]);
const set2 = new Set([3, 4, 5]);
const set3 = new Set([5, 6, 7]);
const mergedSet = customSetMerge([set1, set2, set3]);
console.log(Array.from(mergedSet)); // [1, 2, 3, 4, 5, 6, 7]These utilities are used internally by deepmerge-ts but can also be leveraged in custom merge functions:
import {
getObjectType,
ObjectType,
getKeys,
objectHasProperty,
deepmergeCustom
} from "deepmerge-ts";
// Example: Smart merge that handles different types intelligently
const smartMerge = deepmergeCustom({
mergeRecords: (records, utils, meta) => {
const result = {};
const allKeys = getKeys(records);
for (const key of allKeys) {
const values = records
.filter(record => objectHasProperty(record, key))
.map(record => record[key]);
if (values.length === 1) {
result[key] = values[0];
} else if (values.length > 1) {
// Determine merge strategy based on value types
const firstType = getObjectType(values[0]);
const allSameType = values.every(v => getObjectType(v) === firstType);
if (allSameType && firstType === ObjectType.ARRAY) {
// Concatenate arrays
result[key] = values.flat();
} else if (allSameType && firstType === ObjectType.RECORD) {
// Recursively merge objects
result[key] = utils.deepmerge(values);
} else {
// Use last value for mixed or primitive types
result[key] = values[values.length - 1];
}
}
}
return result;
}
});
// Usage
const result = smartMerge(
{
arrays: [1, 2],
objects: { a: 1 },
mixed: "string",
numbers: 42
},
{
arrays: [3, 4],
objects: { b: 2 },
mixed: 123,
numbers: 84
}
);
// Result: {
// arrays: [1, 2, 3, 4],
// objects: { a: 1, b: 2 },
// mixed: 123,
// numbers: 84
// }