nhb-toolbox
Version:
A versatile collection of smart, efficient, and reusable utility functions and classes for everyday development needs.
268 lines (267 loc) • 10.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseJsonToObject = exports.extractUpdatedAndNewFields = exports.extractNewFields = exports.extractUpdatedFields = exports.flattenObjectDotNotation = exports.flattenObjectKeyValue = exports.mergeAndFlattenObjects = exports.mergeObjects = void 0;
const guards_1 = require("../date/guards");
const guards_2 = require("../form/guards");
const non_primitives_1 = require("../guards/non-primitives");
const index_1 = require("../utils/index");
const sanitize_1 = require("./sanitize");
/**
* Deeply merges two or more objects.
* Objects are merged recursively. Later values override earlier ones unless both are plain objects.
*
* @param objects - List of objects to be merged.
* @returns A new object with deeply merged properties from all input objects.
*
* @example
* const obj1 = { a: 1, b: { x: 10 } };
* const obj2 = { b: { y: 20 }, c: 3 };
* const merged = mergeObjects(obj1, obj2);
* // merged = { a: 1, b: { x: 10, y: 20 }, c: 3 }
*
* @example
* mergeObjects(
* { a: 1, b: 2 },
* { p: { c: 3 }, d: 4 },
* { p: { e: 5 }, f: 6 }
* );
* // => { a: 1, b: 2, p: { c: 3, e: 5 }, d: 4, f: 6 }
*/
const mergeObjects = (...objects) => {
const map = new Map();
for (const obj of objects) {
for (const key in obj) {
const existingValue = map.get(key);
if ((0, non_primitives_1.isNotEmptyObject)(obj[key])) {
if ((0, non_primitives_1.isNotEmptyObject)(existingValue)) {
if ((0, guards_1.isDateLike)(obj[key]) || (0, guards_2.isFileOrBlob)(obj[key])) {
map.set(key, obj[key]);
}
else {
map.set(key, (0, exports.mergeObjects)(existingValue, obj[key]));
}
}
else {
map.set(key, obj[key]);
}
}
else {
map.set(key, obj[key]);
}
}
}
const result = {};
map?.forEach((value, key) => {
result[key] = value;
});
return result;
};
exports.mergeObjects = mergeObjects;
/**
* * Deeply merge objects and flatten nested objects.
* * Useful for flattening a single object or merging multiple objects with duplicate key(s).
* * If keys are duplicated, the last object's value will be used.
*
* @param objects Objects to merge.
* @returns Merged object with flattened structure.
*/
const mergeAndFlattenObjects = (...objects) => {
const map = new Map();
const _flattenObject = (obj, parentKey = '') => {
for (const key in obj) {
const newKey = parentKey ? `${String(parentKey)}.${key}` : key;
if ((0, non_primitives_1.isNotEmptyObject)(obj[key])) {
if ((0, guards_1.isDateLike)(obj[key]) || (0, guards_2.isFileOrBlob)(obj[key])) {
map.set(newKey, obj[key]);
}
else {
_flattenObject(obj[key], newKey);
}
}
else {
map.set(newKey, obj[key]);
}
}
};
for (const obj of objects) {
_flattenObject(obj);
}
const result = {};
map?.forEach((value, key) => {
result[key] = value;
});
return result;
};
exports.mergeAndFlattenObjects = mergeAndFlattenObjects;
/**
* * Flattens a nested object into key-value format.
*
* @param object - The `object` to flatten.
* @returns A `flattened object` in key-value format.
*/
const flattenObjectKeyValue = (object) => {
const flattened = {};
for (const [key, value] of Object.entries(object)) {
if ((0, non_primitives_1.isNotEmptyObject)(value)) {
const nestedFlattened = (0, exports.flattenObjectKeyValue)(value);
if ((0, guards_1.isDateLike)(value) || (0, guards_2.isFileOrBlob)(value)) {
flattened[key] = value;
}
else {
Object.assign(flattened, nestedFlattened);
}
}
else {
flattened[key] = value;
}
}
return flattened;
};
exports.flattenObjectKeyValue = flattenObjectKeyValue;
/**
* * Flattens a nested object into a dot notation format.
*
* @param object - The `object` to flatten.
* @returns A `flattened object` with dot notation keys.
*/
const flattenObjectDotNotation = (object) => {
/**
* * Recursively flattens an object, transforming nested structures into dot-notation keys.
*
* @param source - The `object` to be flattened.
* @param prefix - The prefix to prepend to each key. Used for nested objects.
* @returns A flattened version of the input object.
*/
const _flattenObject = (source, prefix = '') => {
const flattened = {};
for (const [key, value] of Object.entries(source)) {
const newKey = prefix ? `${String(prefix)}.${key}` : key;
if ((0, non_primitives_1.isNotEmptyObject)(value) || (0, guards_2.isFileOrBlob)(value)) {
if ((0, guards_1.isDateLike)(value)) {
flattened[newKey] = value;
}
else {
Object.assign(flattened, _flattenObject(value, newKey));
}
}
else {
flattened[newKey] = value;
}
}
return flattened;
};
return _flattenObject(object);
};
exports.flattenObjectDotNotation = flattenObjectDotNotation;
/**
* * Extracts only the fields that have changed between the original and updated object.
*
* @param baseObject The original object to compare against.
* @param updatedObject The modified object containing potential updates.
* @returns A new object containing only the changed fields.
*/
const extractUpdatedFields = (baseObject, updatedObject) => {
const updatedFields = {};
for (const key in updatedObject) {
if (key in baseObject &&
!(0, index_1.isDeepEqual)(updatedObject[key], baseObject[key])) {
if (updatedObject[key] && (0, non_primitives_1.isNotEmptyObject)(updatedObject[key])) {
updatedFields[key] = (0, exports.extractUpdatedFields)(baseObject[key], updatedObject[key]);
if (updatedFields[key] && (0, non_primitives_1.isEmptyObject)(updatedFields[key])) {
delete updatedFields[key];
}
}
else {
updatedFields[key] = updatedObject[key];
}
}
}
return updatedFields;
};
exports.extractUpdatedFields = extractUpdatedFields;
/**
* * Extracts only new fields that exist in updatedObject but not in baseObject.
*
* @param baseObject The original object to compare against.
* @param updatedObject The modified object containing potential new fields.
* @returns A new object containing only the new fields.
*/
const extractNewFields = (baseObject, updatedObject) => {
const newFields = {};
for (const key in updatedObject) {
if (!(key in baseObject)) {
// Directly assign new fields
newFields[key] = updatedObject[key];
}
else if ((0, non_primitives_1.isNotEmptyObject)(updatedObject[key]) &&
(0, non_primitives_1.isNotEmptyObject)(baseObject[key])) {
// Recursively extract new fields inside nested objects
const nestedNewFields = (0, exports.extractNewFields)(baseObject[key], updatedObject[key]);
if ((0, non_primitives_1.isNotEmptyObject)(nestedNewFields)) {
newFields[key] =
nestedNewFields;
}
}
}
return newFields;
};
exports.extractNewFields = extractNewFields;
/**
* * Extracts changed fields from the updated object while also identifying newly added keys.
*
* @param baseObject The original object to compare against.
* @param updatedObject The modified object containing potential updates.
* @returns An object containing modified fields and new fields separately.
*/
const extractUpdatedAndNewFields = (baseObject, updatedObject) => {
const updatedFields = {};
const newFields = {};
for (const key in updatedObject) {
if (!(key in baseObject)) {
newFields[key] = updatedObject[key];
}
else if (!(0, index_1.isDeepEqual)(updatedObject[key], baseObject[key])) {
if (updatedObject[key] && (0, non_primitives_1.isNotEmptyObject)(updatedObject[key])) {
updatedFields[key] = (0, exports.extractUpdatedAndNewFields)(baseObject[key], updatedObject[key]);
if (updatedFields[key] && (0, non_primitives_1.isEmptyObject)(updatedFields[key])) {
delete updatedFields[key];
}
}
else {
updatedFields[key] = updatedObject[key];
}
}
}
return { ...updatedFields, ...newFields };
};
exports.extractUpdatedAndNewFields = extractUpdatedAndNewFields;
/**
* * Safely parses a JSON string into an object.
* * Optionally converts stringified primitive values inside the object (e.g., `"0"` → `0`, `"true"` → `true`, `"null"` → `null`).
*
* @param value - The JSON string to parse.
* @param parsePrimitives - Whether to convert stringified primitives into real values (default: `true`).
* @returns A parsed object with primitive conversions, or an empty object on failure or if the root is not a valid object.
* - Returns `{}` if parsing fails, such as when the input is malformed or invalid JSON or passing single quoted string.
*
* - **N.B.** This function will return an empty object if the JSON string is invalid or if the root element is not an object.
*
* - *Unlike `parseJSON`, which returns any valid JSON structure (including arrays, strings, numbers, etc.),
* this function strictly ensures that the result is an object and optionally transforms stringified primitives.*
*
* @see parseJSON - For parsing generic JSON values (arrays, numbers, etc.) with optional primitive transformation.
*
*/
const parseJsonToObject = (value, parsePrimitives = true) => {
try {
const data = JSON.parse(value);
if (!(0, non_primitives_1.isObject)(data)) {
return {};
}
return parsePrimitives ? (0, sanitize_1.parseObjectValues)(data) : data;
}
catch {
return {};
}
};
exports.parseJsonToObject = parseJsonToObject;