@rimbu/deep
Version:
Tools to use handle plain JS objects as immutable objects
108 lines • 3.84 kB
JavaScript
import { Deep } from './internal.mjs';
export var Path;
(function (Path) {
/**
* Regular expression used to split a path string into tokens.
*/
Path.stringSplitRegex = /\?\.|\.|\[|\]/g;
/**
* Return the given `path` string split into an array of subpaths.
* @param path - the input string path
*/
function stringSplit(path) {
return path.split(Path.stringSplitRegex);
}
Path.stringSplit = stringSplit;
})(Path || (Path = {}));
/**
* Returns the value resulting from selecting the given `path` in the given `source` object.
* It supports optional chaining for nullable values or values that may be undefined, and also
* for accessing objects inside an array.
* There is currently no support for forcing non-null (the `!` operator).
* @typeparam T - the object type to select in
* @typeparam P - a Path in object type T
* @param source - the object to select in
* @param path - the path into the object
* @example
* ```ts
* const value = { a: { b: { c: [{ d: 5 }, { d: 6 }] } } }
* Deep.getAt(value, 'a.b');
* // => { c: [{ d: 5 }, { d: 6 }] }
* Deep.getAt(value, 'a.b.c');
* // => [{ d: 5 }, { d: 6 }]
* Deep.getAt(value, 'a.b.c[1]');
* // => { d: 6 }
* Deep.getAt(value, 'a.b.c[1]?.d');
* // => 6
* ```
*/
export function getAt(source, path) {
if (path === '') {
// empty path always directly returns source value
return source;
}
const items = Path.stringSplit(path);
// start with `source` as result value
let result = source;
for (const item of items) {
if (undefined === item || item === '' || item === '[') {
// ignore irrelevant items
continue;
}
if (undefined === result || null === result) {
// optional chaining assumed and no value available, skip rest of path and return undefined
return undefined;
}
// set current result to subpath value
result = result[item];
}
return result;
}
/**
* Patches the value at the given path in the source to the given value.
* Because the path to update must exist in the `source` object, optional
* chaining and array indexing is not allowed.
* @param source - the object to update
* @param path - the path in the object to update
* @param patchItem - the patch for the value at the given path
* @example
* ```ts
* const value = { a: { b: { c: 5 } } };
* Deep.patchAt(value, 'a.b.c', v => v + 5);
* // => { a: { b: { c: 6 } } }
* ```
*/
export function patchAt(source, path, patchItem) {
if (path === '') {
return Deep.patch(source, patchItem);
}
const items = Path.stringSplit(path);
// creates a patch object based on the current path
function createPatchPart(index, target) {
if (index === items.length) {
// processed all items, return the input `patchItem`
return patchItem;
}
const item = items[index];
if (undefined === item || item === '') {
// empty items can be ignored
return createPatchPart(index + 1, target);
}
if (item === '[') {
// next item is array index, set arrayMode to true
return createPatchPart(index + 1, target);
}
// create object with subPart as property key, and the restuls of processing next parts as value
const result = {
[item]: createPatchPart(index + 1, target[item]),
};
if (Array.isArray(target)) {
// target in source object is array/tuple, so the patch should be object
return result;
}
// target in source is not an array, so it patch should be an array
return [result];
}
return Deep.patch(source, createPatchPart(0, source));
}
//# sourceMappingURL=path.mjs.map