super-utils-plus
Version:
A superior alternative to Lodash with improved performance, TypeScript support, and developer experience
112 lines (111 loc) • 3.46 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.set = set;
const is_1 = require("../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 } }] }
* ```
*/
function set(object, path, value) {
if ((0, is_1.isNil)(object)) {
return object;
}
const segments = (0, is_1.isString)(path) ? parsePath(path) :
(0, is_1.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;
}