UNPKG

super-utils-plus

Version:

A superior alternative to Lodash with improved performance, TypeScript support, and developer experience

109 lines (108 loc) 3.36 kB
import { isArray, isNil, isString } from '../utils/is'; /** * Sets the value at path of object. If a portion of path doesn't exist, it's created. * Arrays are created for missing index properties while objects are created for all * other missing properties. * * @param object - The object to modify * @param path - The path of the property to set * @param value - The value to set * @returns The modified object * * @example * ```ts * const object = { 'a': [{ 'b': { 'c': 3 } }] }; * * set(object, 'a[0].b.c', 4); * // => { 'a': [{ 'b': { 'c': 4 } }] } * * set(object, ['x', '0', 'y', 'z'], 5); * // => { 'a': [{ 'b': { 'c': 4 } }], 'x': [{ 'y': { 'z': 5 } }] } * ``` */ export function set(object, path, value) { if (isNil(object)) { return object; } const segments = isString(path) ? parsePath(path) : isArray(path) ? path : [path]; if (!segments.length) { return object; } const root = object; const lastIndex = segments.length - 1; // Create nested properties if they don't exist let current = root; for (let i = 0; i < lastIndex; i++) { const segment = segments[i]; const nextSegment = segments[i + 1]; const isNextIndexProperty = typeof nextSegment === 'number' || /^\d+$/.test(String(nextSegment)); // If the current segment doesn't exist, create it if (current[segment] === undefined) { // Create an array if the next segment is an index, otherwise an object current[segment] = isNextIndexProperty ? [] : {}; } current = current[segment]; // If we unexpectedly hit a primitive value, replace it with an object or array if (current === null || typeof current !== 'object') { current = root[segment] = isNextIndexProperty ? [] : {}; } } // Set the final value current[segments[lastIndex]] = value; return root; } /** * Parses a string path into path segments. * Supports dot notation and bracket notation. * * @param path - The path to parse * @returns An array of path segments * * @example * ```ts * parsePath('a[0].b.c'); * // => ['a', 0, 'b', 'c'] * ``` */ function parsePath(path) { // Split by dots, but not within brackets const segments = []; let currentSegment = ''; let inBrackets = false; for (let i = 0; i < path.length; i++) { const char = path[i]; if (char === '[') { if (currentSegment) { segments.push(currentSegment); currentSegment = ''; } inBrackets = true; } else if (char === ']') { // If it's a number in brackets, convert to number if (/^\d+$/.test(currentSegment)) { segments.push(parseInt(currentSegment, 10)); } else { segments.push(currentSegment); } currentSegment = ''; inBrackets = false; } else if (char === '.' && !inBrackets) { if (currentSegment) { segments.push(currentSegment); currentSegment = ''; } } else { currentSegment += char; } } if (currentSegment) { segments.push(currentSegment); } return segments; }