daily-toolset
Version:
A lightweight, versatile collection of TypeScript utility functions for everyday development needs. Simplify and streamline your Node.js, React, and Next.js projects with a powerful suite of well-organized helpers for strings, arrays, dates, objects, and
250 lines (249 loc) • 8.06 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.transformObject = transformObject;
exports.pickFromObject = pickFromObject;
exports.omitFromObject = omitFromObject;
exports.prependToObjectKey = prependToObjectKey;
exports.deepMerge = deepMerge;
exports.jsonify = jsonify;
const dateUtils_1 = require("./dateUtils");
/**
* Transforms a flat object with dot-separated keys into a nested object structure.
*
* The function takes an object with keys that may contain dot notation to represent
* nested structures and returns a new object with the corresponding nested structure.
* If a key part is numeric, it will be treated as an array index.
*
* @param {ObjectTransformInput} obj - The input object with flat, dot-separated keys.
* @returns {Transformed} - The transformed object with nested structure.
*
* @throws {Error} Throws an error if the input is not an object or if the key structure is invalid.
*
* @example
* const input = {
* "a.b": 1,
* "a.c": 2,
* "d": 3,
* "e.0": 4,
* "e.1": 5
* };
* const result = transformObject(input);
* console.log(result);
* // Output: { a: { b: 1, c: 2 }, d: 3, e: [4, 5] }
*/
function transformObject(obj) {
if (!obj || typeof obj !== "object") {
throw new Error("Invalid input: Expected an object");
}
const result = {};
Object.keys(obj).forEach((key) => {
const value = obj[key];
const parts = key.split(".");
let current = result;
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
if (i === parts.length - 1) {
if (/^\d+$/.test(part)) {
if (!Array.isArray(current))
current = [];
current[parseInt(part, 10)] = value;
}
else {
current[part] = value;
}
}
else {
const nextPartIsNumeric = /^\d+$/.test(parts[i + 1]);
if (!current[part]) {
current[part] = nextPartIsNumeric ? [] : {};
}
if (typeof current[part] !== "object" || current[part] === null) {
throw new Error(`Invalid structure at key part: ${part}`);
}
current = current[part];
}
}
});
return result;
}
/**
* Creates a new object containing only the specified keys from the original object.
*
* @template T The type of the object from which keys will be picked.
* @template K The type of the keys to pick from the object.
*
* @param {PickFromObjectParams<T, K>} params - An object containing the original object and the keys to pick.
* @param {T | undefined} params.obj - The original object from which keys will be picked.
* @param {K[]} params.keys - An array of keys to pick from the object.
* @returns {Pick<T, K>} A new object containing only the picked keys.
*/
function pickFromObject({ obj, keys }) {
if (!obj)
return {};
const result = {};
keys.forEach((key) => {
if (key in obj) {
result[key] = obj[key];
}
});
return result;
}
/**
* Creates a new object excluding the specified keys from the original object.
*
* @template T The type of the object from which keys will be omitted.
* @template K The type of the keys to omit from the object.
*
* @param {OmitFromObjectParams<T, K>} params - An object containing the original object and the keys to omit.
* @param {T | undefined} params.obj - The original object from which keys will be omitted.
* @param {K[]} params.keys - An array of keys to omit from the object.
* @returns {Omit<T, K>} A new object excluding the omitted keys.
*/
function omitFromObject({ obj, keys }) {
if (!obj)
return {};
const result = { ...obj };
keys.forEach((key) => {
if (key in result) {
delete result[key];
}
});
return result;
}
/**
* Creates a new object by prepending a specified string to each key of the original object.
*
* @param {object} obj - The original object whose keys will be modified.
* @param {string} key - The string to prepend to each key.
* @returns {object} A new object with modified keys.
*
* @example
* const original = { name: 'Alice', age: 30 };
* const prepended = prependToObjectKey(original, 'user_');
* // Result: { user_name: 'Alice', user_age: 30 }
*/
function prependToObjectKey(obj, key) {
if (!obj) {
return {}; // Return an empty object if obj is null or undefined
}
const result = {};
Object.keys(obj).forEach((item) => {
const newKey = `${key}${item}`;
result[newKey] = obj[item];
});
return result;
}
/**
* A TypeScript utility function to perform a deep merge of two objects, allowing for selective merging of nested properties.
*
* @param {object} target - The original object to merge into.
* @param {object} source - The object containing updates. Only properties in `source` will overwrite target properties.
* @returns {object} An object of type `T` with `target` properties overwritten by `source` properties where applicable.
*
* @example
* const original = {
* user: {
* name: 'John',
* address: {
* city: 'New York',
* zip: '10001'
* }
* }
* };
*
* const updates = {
* user: {
* address: {
* city: 'San Francisco'
* }
* }
* };
*
* const merged = deepMerge({ target: original, source: updates });
* console.log(merged);
* /*
* {
* user: {
* name: 'John',
* address: {
* city: 'San Francisco',
* zip: '10001'
* }
* }
* }
*/
function deepMerge({ target, source, }) {
const output = { ...target };
for (const key in source) {
const targetValue = target[key];
const sourceValue = source[key];
if (isObject(targetValue) && isObject(sourceValue)) {
// Recursively merge objects
output[key] = deepMerge({
target: targetValue,
source: sourceValue,
});
}
else if (sourceValue !== undefined) {
output[key] = sourceValue;
}
}
return output;
}
/**
* Determines if the provided value is a non-null object.
*
* This function checks whether a given value is of type 'object'
* and is not null. It is useful for type guarding to ensure that
* a value is a valid object before performing operations on it.
*
* @param {unknown} value - The value to check.
* @returns {boolean} - True if the value is a non-null object, false otherwise.
*/
function isObject(value) {
return typeof value === "object" && value !== null;
}
/**
* Converts the provided data to a JSON-compatible format by replacing
* big integers with either numbers or strings and reviving date strings
* to Date objects.
*
* @param {object | object[]} data - The data to serialize.
* @returns {object | object[]} - The serialized data.
*
* @example
* const data = {
* id: 1234567890123456789,
* name: "John Doe",
* date: "2022-01-01T12:00:00.000Z",
* };
*
* const jsonified = jsonify(data);
* // Result: { id: "1234567890123456789", name: "John Doe", date: Date object }
*/
function jsonify(data) {
if (typeof data !== "object" || !Array.isArray(data))
return data;
const replacer = (key, value) => {
if (typeof value === "bigint") {
return value <= Number.MAX_SAFE_INTEGER
? Number(value)
: value.toString();
}
return value;
};
const reviver = (key, value) => {
if (typeof value === "string" && (0, dateUtils_1.isValidDate)(value)) {
return new Date(value);
}
return value;
};
try {
const serialized = JSON.stringify(data, replacer);
return JSON.parse(serialized, reviver);
}
catch (error) {
console.error("TransformData error:", error);
return data;
}
}
;