UNPKG

lodash-walk-object

Version:

Walk all properties deep in object with lodash help

446 lines (441 loc) 15.9 kB
import { _ } from 'tnp-core/browser'; class Helpers { // eslint-disable-next-line @typescript-eslint/explicit-function-return-type static get Walk() { const self = this; return { Object(json, iterator, options = {}) { const internalOptions = self.createInternalOptions(options, _.isFunction(iterator)); const result = self._walk(json, json, iterator, '', internalOptions); return { circs: result.circural, }; }, ObjectBy(property, inContext, iterator, options = {}) { const contextRecord = inContext; if (_.isFunction(iterator)) { iterator(inContext, '', self._changeValue(inContext, property, true)); } const json = contextRecord[property]; if (!self.isObjectLike(json)) { return { circs: undefined, }; } return self.Walk.Object(json, iterator, options); }, }; } static createInternalOptions(options, hasIterator) { const internalOptions = { ...options, walkGetters: options.walkGetters ?? true, checkCircural: options.checkCircural ?? false, breadthWalk: options.breadthWalk ?? false, considerSharedObjects: options.considerSharedObjects ?? false, include: options.include ?? [], exclude: options.exclude ?? [], isGetter: false, isCircural: false, hasIterator, _valueChanged: false, _skip: false, _exit: false, }; internalOptions.skipObject = () => { internalOptions._skip = true; }; internalOptions.exit = () => { internalOptions._exit = true; }; if (internalOptions.checkCircural) { internalOptions.stack = []; internalOptions.circural = []; } return internalOptions; } static _walk(json, currentValue, iterator, lodashPath, options) { if (options.breadthWalk) { return this.walkBreadthFirst(json, iterator, lodashPath, options); } this.walkDepthFirst(json, currentValue, iterator, lodashPath, options, false); return options; } static walkDepthFirst(json, currentValue, iterator, lodashPath, options, isGetter) { if (options._exit) { return; } if (this.shouldSkipPath(options.include, options.exclude, lodashPath)) { return; } const pushedToStack = this.prepareCurrentValue(currentValue, lodashPath, options, isGetter); try { if (options._exit) { return; } if (options.hasIterator && lodashPath !== '') { iterator(currentValue, lodashPath, this._changeValue(json, lodashPath, false, options), options); } if (options._exit) { return; } if (options._valueChanged) { currentValue = _.get(json, lodashPath); options._valueChanged = false; } if (options.isCircural) { return; } if (options._skip) { options._skip = false; return; } const children = this.findChildren(currentValue, lodashPath, options.walkGetters ?? true); for (const child of children) { if (options._exit) { return; } this.walkDepthFirst(json, child.value, iterator, child.path, options, child.isGetter); } } finally { if (pushedToStack && options.considerSharedObjects) { options.stack?.pop(); } } } static walkBreadthFirst(json, iterator, lodashPath, options) { const queue = [ { v: json, p: lodashPath, isGetter: false, ancestors: [], }, ]; while (queue.length > 0) { if (options._exit) { break; } const current = queue.shift(); if (!current) { break; } if (this.shouldSkipPath(options.include, options.exclude, current.p)) { continue; } options.isGetter = current.isGetter ?? false; options.isCircural = false; let childAncestors = current.ancestors ?? []; if (options.checkCircural && this.isObjectLike(current.v)) { if (options.considerSharedObjects) { const existingAncestor = childAncestors.find(entry => entry.target === current.v); if (existingAncestor) { options.circural ??= []; options.circural.push({ pathToObj: current.p, circuralTargetPath: existingAncestor.path, }); options.isCircural = true; } else { childAncestors = [ ...childAncestors, { target: current.v, path: current.p, }, ]; } } else { options.stack ??= []; options.circural ??= []; const existing = options.stack.find(entry => entry.target === current.v); if (existing) { options.circural.push({ pathToObj: current.p, circuralTargetPath: existing.path, }); options.isCircural = true; } else { options.stack.push({ target: current.v, path: current.p, }); } } } if (options.hasIterator && current.p !== '') { iterator(current.v, current.p, this._changeValue(json, current.p, false, options), options); } if (options._exit) { break; } if (options._valueChanged) { current.v = _.get(json, current.p); options._valueChanged = false; } if (options.isCircural) { continue; } if (options._skip) { options._skip = false; continue; } const children = this.findChildren(current.v, current.p, options.walkGetters ?? true); for (const child of children) { queue.push({ v: child.value, p: child.path, parent: current, isGetter: child.isGetter, ancestors: childAncestors, }); } } return options; } static prepareCurrentValue(value, lodashPath, options, isGetter) { options.isGetter = isGetter; options.isCircural = false; if (!options.checkCircural || !this.isObjectLike(value)) { return false; } const stack = options.stack ?? []; const circular = options.circural ?? []; options.stack = stack; options.circural = circular; const existing = stack.find(entry => entry.target === value); if (existing) { circular.push({ pathToObj: lodashPath, circuralTargetPath: existing.path, }); options.isCircural = true; return false; } stack.push({ target: value, path: lodashPath, }); return true; } static findChildren(value, parentPath, walkGetters) { if (Array.isArray(value)) { return value.map((child, index) => ({ value: child, path: `${parentPath}[${index}]`, isGetter: false, })); } if (!this.isObjectLike(value)) { return []; } const entries = this.getPropertyEntries(value, walkGetters); return entries.map(entry => ({ value: entry.value, path: this.appendPath(parentPath, entry.key), isGetter: entry.isGetter, })); } static getPropertyEntries(object, walkGetters) { const result = []; const handledKeys = new Set(); for (const key of Object.keys(object)) { const readResult = this.readProperty(object, key); if (!readResult.success) { continue; } handledKeys.add(key); result.push({ key, value: readResult.value, isGetter: false, }); } if (!walkGetters) { return result; } for (const key of Object.getOwnPropertyNames(object)) { if (handledKeys.has(key)) { continue; } const readResult = this.readProperty(object, key); if (!readResult.success) { continue; } handledKeys.add(key); result.push({ key, value: readResult.value, isGetter: true, }); } let prototype = Object.getPrototypeOf(object); while (prototype && prototype !== Object.prototype) { const descriptors = Object.getOwnPropertyDescriptors(prototype); for (const [key, descriptor] of Object.entries(descriptors)) { if (key === 'constructor' || handledKeys.has(key) || typeof descriptor.get !== 'function') { continue; } const readResult = this.readProperty(object, key); if (!readResult.success) { continue; } handledKeys.add(key); result.push({ key, value: readResult.value, isGetter: true, }); } prototype = Object.getPrototypeOf(prototype); } return result; } static readProperty(object, key) { try { return { success: true, value: Reflect.get(object, key), }; } catch { return { success: false, }; } } static appendPath(parentPath, property) { const propertyPath = this.propertyToPath(property); if (parentPath === '') { return propertyPath; } if (propertyPath.startsWith('[')) { return `${parentPath}${propertyPath}`; } return `${parentPath}.${propertyPath}`; } static propertyToPath(property) { /** * Preserve the old path format for keys that lodash can safely * interpret in dot notation. * * Only keys containing dots or brackets need quoted bracket syntax, * because those characters change the lodash path meaning. */ if (property !== '' && !property.includes('.') && !property.includes('[') && !property.includes(']')) { return property; } return `[${JSON.stringify(property)}]`; } static shouldSkipPath(include = [], exclude = [], lodashPath) { const normalizedPath = this.normalizeRootArrayPath(lodashPath); if (normalizedPath === '') { return false; } const excluded = exclude.some(excludedPath => this.isSamePathOrDescendant(normalizedPath, excludedPath)); if (excluded) { return true; } if (include.length === 0) { return false; } const includedOrRequiredParent = include.some(includedPath => { return (this.isSamePathOrDescendant(normalizedPath, includedPath) || this.isAncestorPath(normalizedPath, includedPath)); }); return !includedOrRequiredParent; } static normalizeRootArrayPath(path) { return path.replace(/^\[(?:'|")?\d+(?:'|")?\]\.?/, '').trim(); } static isSamePathOrDescendant(path, parentPath) { return (path === parentPath || path.startsWith(`${parentPath}.`) || path.startsWith(`${parentPath}[`)); } static isAncestorPath(possibleAncestor, path) { return (path.startsWith(`${possibleAncestor}.`) || path.startsWith(`${possibleAncestor}[`)); } static _changeValue(json, lodashPath, simpleChange = false, options) { const { contextPath, property } = this._prepareParams(lodashPath); return (newValue) => { if (contextPath === '') { simpleChange = true; } if (simpleChange) { Reflect.set(json, property, newValue); } else { const context = _.get(json, contextPath); if (this.isObjectLike(context)) { Reflect.set(context, property, newValue); } } if (options) { options._valueChanged = true; } }; } static _prepareParams(lodashPath) { const contextPath = this._Helpers.Path.getContextPath(lodashPath); const rawProperty = this._Helpers.Path.getPropertyPath(lodashPath, contextPath); const property = rawProperty.trim() !== '' && !Number.isNaN(Number(rawProperty)) ? Number(rawProperty) : rawProperty; return { contextPath, property, }; } // eslint-disable-next-line @typescript-eslint/explicit-function-return-type static get _Helpers() { return { get Path() { return { getPropertyPath(lodashPath, contextPath) { return lodashPath .replace(contextPath, '') .replace(/^\./, '') .replace(/^\[/, '') .replace(/\]$/, '') .replace(/^["']|["']$/g, ''); }, getContextPath(path) { let result; if (path.endsWith(']')) { result = path.replace(/\[(?:"|')?.+?(?:"|')?\]$/, ''); } else { result = path.replace(/\.([a-zA-Z0-9_$@\-/:]+)$/, ''); } return result === path ? '' : result; }, }; }, }; } static isObjectLike(value) { return (value !== null && (typeof value === 'object' || typeof value === 'function')); } } const walk = { Object: Helpers.Walk.Object, ObjectBy: Helpers.Walk.ObjectBy, }; // export * from './start-cli'; // @backend /** * Generated bundle index. Do not edit. */ export { Helpers, walk }; //# sourceMappingURL=lodash-walk-object-browser.mjs.map