UNPKG

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
"use strict"; 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;