houser-js-utils
Version:
A comprehensive collection of TypeScript utility functions for common development tasks including array manipulation, string processing, date handling, random number generation, validation, and much more.
482 lines (481 loc) • 14.3 kB
JavaScript
const ObjectUtils = {
/**
* Creates a deep clone of an object, including nested objects and arrays.
* @template T - The type of the object to clone
* @param obj - The object to clone
* @returns A deeply cloned copy of the object
* @example
* ```typescript
* const original = { user: { name: 'John', hobbies: ['reading'] } };
* const cloned = ObjectUtils.cloneObject(original);
* cloned.user.name = 'Jane'; // Original remains unchanged
* ```
*/
cloneObject(obj) {
if (obj === null || typeof obj !== "object") {
return obj;
}
if (Array.isArray(obj)) {
return obj.map((item) => this.cloneObject(item));
}
const clonedObj = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
clonedObj[key] = this.cloneObject(
obj[key]
);
}
}
return clonedObj;
},
/**
* Performs a deep equality check between two values, comparing all nested properties.
* @param obj1 - The first value to compare
* @param obj2 - The second value to compare
* @returns True if the values are deeply equal, false otherwise
* @example
* ```typescript
* ObjectUtils.deepEqual({ a: { b: 1 } }, { a: { b: 1 } }); // Returns true
* ObjectUtils.deepEqual({ a: 1 }, { a: 2 }); // Returns false
* ObjectUtils.deepEqual([1, 2, 3], [1, 2, 3]); // Returns true
* ```
*/
deepEqual(obj1, obj2) {
if (obj1 === obj2) {
return true;
}
if (typeof obj1 !== "object" || obj1 === null || typeof obj2 !== "object" || obj2 === null) {
return false;
}
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) {
return false;
}
return !keys1.some(
(key) => !Object.prototype.hasOwnProperty.call(obj2, key) || !this.deepEqual(obj1[key], obj2[key])
);
},
/**
* Filters an array of objects to unique values based on a specified property.
* @template T - The type of objects in the array
* @param objects - The array of objects to filter
* @param prop - The property to use for uniqueness comparison
* @returns An array containing only unique objects based on the specified property
* @example
* ```typescript
* const users = [
* { id: 1, name: 'John' },
* { id: 2, name: 'Jane' },
* { id: 1, name: 'John Doe' }
* ];
* ObjectUtils.filterUniqueByProp(users, 'id'); // Returns first two objects
* ```
*/
filterUniqueByProp(objects, prop) {
if (!objects?.length) return [];
const flags = {};
return objects.filter((o) => {
if (flags[String(o[prop])]) return false;
flags[String(o[prop])] = true;
return true;
});
},
/**
* Filters an object to only include the specified keys.
* @param obj - The object to filter
* @param keys - An array of keys to include in the filtered object
* @returns A new object containing only the specified keys
* @example
* ```typescript
* const user = { id: 1, name: 'John', email: 'john@example.com', password: 'secret' };
* ObjectUtils.filterByKeys(user, ['id', 'name']); // Returns { id: 1, name: 'John' }
* ```
*/
filterByKeys(obj, keys) {
return Object.fromEntries(
Object.entries(obj).filter(([key]) => keys.includes(key))
);
},
/**
* Finds the index of the first non-zero value in an object of numbers.
* @param data - An object containing numeric values
* @returns The index of the first non-zero value, or -1 if all values are zero
* @example
* ```typescript
* ObjectUtils.findFirstNonZeroIndex({ a: 0, b: 0, c: 5, d: 2 }); // Returns 2
* ObjectUtils.findFirstNonZeroIndex({ a: 0, b: 0 }); // Returns -1
* ```
*/
findFirstNonZeroIndex(data) {
return Object.entries(data).findIndex(([_, value]) => value !== 0);
},
/**
* Flattens a nested object into a single-level object with dot-notation keys.
* @param obj - The object to flatten
* @param prefix - The prefix to use for nested keys (default: empty string)
* @returns A flattened object with dot-notation keys
* @example
* ```typescript
* const nested = { user: { profile: { name: 'John', age: 30 } } };
* ObjectUtils.flatten(nested);
* // Returns { 'user.profile.name': 'John', 'user.profile.age': 30 }
* ```
*/
flatten(obj, prefix = "") {
return Object.keys(obj).reduce(
(acc, key) => {
const prefixedKey = prefix ? `${prefix}.${key}` : key;
if (typeof obj[key] === "object" && obj[key] !== null && !Array.isArray(obj[key])) {
Object.assign(
acc,
this.flatten(obj[key], prefixedKey)
);
} else {
acc[prefixedKey] = obj[key];
}
return acc;
},
{}
);
},
/**
* Converts a Map to a plain object
* @param map - Map to convert
* @returns Plain object
*/
fromMap(map) {
if (!map) return {};
return map instanceof Map ? Object.fromEntries(map) : map;
},
/**
* Gets a nested property from an object
* @param obj - Object to get property from
* @param path - Path to property
* @returns Property value or null if not found
*/
getNestedProp(obj, ...path) {
if (!obj || path.length === 0 || Object.keys(obj).length === 0) {
return null;
}
let value = obj;
for (const prop of path) {
if (!value || typeof value[prop] === "undefined") {
return null;
}
value = value[prop];
}
if (this.isObject(value) && Object.keys(value).length === 0) {
return null;
}
return value;
},
/**
* Groups objects by a property value
* @param objects - Array of objects to group
* @param propPath - Path to property to group by
* @returns Object with grouped arrays
*/
groupByProp(objects, ...propPath) {
const grouping = {};
if (!objects) {
return grouping;
}
objects.forEach((obj) => {
const val = this.getNestedProp(obj, ...propPath);
if (typeof val === "undefined" || val === null) {
return;
}
const key = String(val);
if (!grouping[key]) {
grouping[key] = [];
}
grouping[key].push(obj);
});
return grouping;
},
/**
* Checks if an object has a nested property
* @param obj - Object to check
* @param path - Path to property
* @returns True if property exists
*/
hasNestedProp(obj, ...path) {
if (!obj || path.length === 0 || Object.keys(obj).length === 0)
return false;
let value = obj;
for (const prop of path) {
if (!value || typeof value[prop] === "undefined") {
return false;
}
value = value[prop];
}
return true;
},
/**
* Checks if an object is empty
* @param obj - Object to check
* @returns True if object is empty
*/
isEmpty(obj) {
return [Object, Array].includes((obj || {}).constructor) && !Object.entries(obj || {}).length;
},
/**
* Checks if a value is a function
* @param value - Value to check
* @returns True if value is a function
*/
isFunction(value) {
return value instanceof Function;
},
/**
* Checks if two objects are equal using JSON stringification
* @param obj1 - First object to compare
* @param obj2 - Second object to compare
* @returns True if objects are equal
*/
isEqual(obj1, obj2) {
if (!obj1 || !obj2) return false;
return JSON.stringify(obj1) === JSON.stringify(obj2);
},
/**
* Compares objects based on specified keys
* @param obj1 - First object to compare
* @param obj2 - Second object to compare
* @param keys - Array of keys to compare
* @returns True if objects are equal on specified keys
*/
isEqualOnKeys(obj1, obj2, keys) {
return this.deepEqual(
this.filterByKeys(obj1, keys),
this.filterByKeys(obj2, keys)
);
},
/**
* Checks if a value is an object
* @param value - Value to check
* @returns True if value is an object
*/
isObject(value) {
return value !== null && typeof value === "object";
},
/**
* Maps objects by a property value
* @param objects - Array of objects to map
* @param propPath - Path to property to map by
* @returns Object with mapped values
*/
mapByProp(objects, ...propPath) {
if (!objects) {
return {};
}
const groups = this.groupByProp(objects, ...propPath);
const result = {};
Object.keys(groups).forEach((k) => {
result[k] = groups[k][0];
});
return result;
},
/**
* Checks if an object conforms to a set of rules
* @param obj - Object to check
* @param ruleSet - Object containing validation functions
* @returns True if object conforms to all rules
*/
matchesRules(obj, ruleSet) {
return Object.keys(ruleSet).every(
(key) => ruleSet[key](obj[key])
);
},
/**
* Deep merges two or more objects
* @param objects - Objects to merge
* @returns Merged object
*/
merge(...objects) {
return objects.reduce((result, current) => {
Object.keys(current).forEach((key) => {
const typedKey = key;
if (this.isObject(current[typedKey]) && this.isObject(result[typedKey])) {
result[typedKey] = this.merge(
result[typedKey],
current[typedKey]
);
} else {
result[typedKey] = current[typedKey];
}
});
return result;
}, {});
},
/**
* Creates a new object with specified keys omitted
* @param obj - Object to omit keys from
* @param keys - Keys to omit
* @returns New object without specified keys
*/
omit(obj, keys) {
const result = {};
Object.keys(obj).forEach((key) => {
if (!keys.includes(key)) {
result[key] = obj[key];
}
});
return result;
},
/**
* Creates a new object with only the specified keys
* @param obj - Object to pick from
* @param keys - Keys to pick
* @returns New object with only specified keys
*/
pick(obj, keys) {
return Object.fromEntries(
Object.entries(obj).filter(([key]) => keys.includes(key))
);
},
/**
* Removes null, undefined, and optionally empty string values from an object
* @param obj - Object to clean
* @param noEmptyStrings - Whether to remove empty strings
* @returns Cleaned object
*/
removeNullishValues(obj, noEmptyStrings = false) {
const result = { ...obj };
Object.keys(result).forEach((key) => {
if (result[key] === null || result[key] === void 0 || noEmptyStrings && result[key] === "") {
delete result[key];
}
});
return result;
},
/**
* Replaces empty strings with null in an object
* @param obj - Object to process
* @param deep - Whether to process nested objects
* @returns Processed object
*/
replaceEmptyStringsWithNull(obj, deep = false) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [
key,
value === "" ? null : deep && typeof value === "object" && value !== null ? Array.isArray(value) ? value.map(
(item) => typeof item === "object" && item !== null ? this.replaceEmptyStringsWithNull(
item,
deep
) : item === "" ? null : item
) : this.replaceEmptyStringsWithNull(
value,
deep
) : value
])
);
},
/**
* Replaces a key in an object
* @param obj - Object to modify
* @param oldKey - Key to replace
* @param newKey - New key name
* @returns New object with replaced key
*/
replaceKey(obj, oldKey, newKey) {
const newObj = {};
for (const key in obj) {
if (key === oldKey) {
newObj[newKey] = obj[oldKey];
} else {
newObj[key] = obj[key];
}
}
return newObj;
},
/**
* Replaces null values with empty strings in an object
* @param obj - Object to process
* @param fields - Optional array of fields to process
* @returns Processed object
*/
replaceNullWithEmptyString(obj, fields) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [
key,
!fields && value === null || fields?.includes(key) && value === null ? "" : value
])
);
},
/**
* Sets a nested property in an object
* @param obj - Object to modify
* @param path - Path to property
* @param value - Value to set
* @returns New object with set property
*/
setNestedProp(obj, ...path) {
if (!obj) {
return null;
}
const value = path.pop();
const props = path;
const localSet = (objParam, props2) => {
const first = props2.shift();
const newObj = { ...objParam };
if (props2.length === 0) {
newObj[first] = value;
} else {
newObj[first] = localSet(
newObj[first] || {},
props2
);
}
return newObj;
};
return localSet(obj, props);
},
/**
* Sorts an array of objects by a property
* @param objects - Array of objects to sort
* @param prop - Property to sort by
* @param direction - Sort direction
* @returns Sorted array
*/
sortByProp({
objects,
prop,
direction = "asc"
}) {
if (!objects) return [];
return [...objects].sort((a, b) => {
const aVal = String(a[prop]);
const bVal = String(b[prop]);
return direction === "asc" ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);
});
},
/**
* Converts an object to a Map
* @param obj - Object to convert
* @returns Map
*/
toMap(obj) {
if (!obj) return /* @__PURE__ */ new Map();
return new Map(Object.entries(obj));
},
/**
* Transforms an object's keys and/or values
* @param obj - Object to transform
* @param keyTransform - Function to transform keys
* @param valueTransform - Function to transform values
* @returns Transformed object
*/
transform(obj, keyTransform, valueTransform) {
return Object.entries(obj).reduce((acc, [key, value]) => {
const newKey = keyTransform ? keyTransform(key) : key;
const newValue = valueTransform ? valueTransform(value) : value;
acc[newKey] = newValue;
return acc;
}, {});
}
};
export {
ObjectUtils
};
//# sourceMappingURL=ObjectUtils.mjs.map