import type {NestedKeys, NestedValue, PlainObject} from '../../models'; import type {Ok} from '../../result/models'; import {getPaths, handleValue} from './misc'; // #region Functions /** * Update the value in an object using a known path * * @param data Object to set value in * @param path Path for value, e.g., `foo.bar.baz` * @param updater Function to update the current value * @returns Updated object * * @example * ```typescript * const data = {foo: {bar: {baz: 42}}}; * * setValue(data, 'foo.bar.baz', (current) => current + 1); // => {foo: {bar: {baz: 43}}} * ``` */ export function setValue>( data: Data, path: Path, updater: (current: NestedValue) => NestedValue, ): Data; /** * Set the value in an object using a known path * * @param data Object to set value in * @param path Path for value, e.g., `foo.bar.baz` * @param value Value to set * @returns Updated object * * @example * ```typescript * const data = {foo: {bar: {baz: 42}}}; * * setValue(data, 'foo.bar.baz', 99); // => {foo: {bar: {baz: 99}}} * ``` */ export function setValue>( data: Data, path: Path, value: NestedValue, ): Data; /** * Set the value in an object using an unknown path * * @param data Object to set value in * @param path Path for value, e.g., `foo.bar.baz` * @param value Value to set * @param ignoreCase If `true`, the path matching is case-insensitive * @returns Updated object * * @example * ```typescript * const data = {foo: {bar: {baz: 42}}}; * * setValue(data, 'Foo.Bar.Baz', 99, true); // => {foo: {bar: {baz: 99}}} * ``` */ export function setValue( data: Data, path: string, value: unknown, ignoreCase?: boolean, ): Data; export function setValue(data: object, path: string, value: unknown, ignoreCase?: boolean): object { if ( typeof data !== 'object' || data === null || typeof path !== 'string' || path.trim().length === 0 ) { return data; } const shouldIgnoreCase = ignoreCase === true; const paths = getPaths(path, shouldIgnoreCase); if (typeof paths === 'string') { handleValue(data, paths, value, false, shouldIgnoreCase); return data; } const {length} = paths; const lastIndex = length - 1; let target = data; for (let index = 0; index < length; index += 1) { const currentPath = paths[index]; if (index === lastIndex) { handleValue(target, currentPath, value, false, shouldIgnoreCase); break; } let next = (handleValue(target, currentPath, null, true, shouldIgnoreCase) as Ok) .value; if (typeof next !== 'object' || next === null) { const nextPath = paths[index + 1]; if (EXPRESSION_INDEX.test(nextPath)) { next = Array.from({length: Number(nextPath) + 1}, () => undefined); } else { next = {}; } (target as PlainObject)[currentPath] = next; } target = next as object; } return data; } // #endregion // #region Variables const EXPRESSION_INDEX = /^\d+$/; // #endregion