UNPKG

object-traversal

Version:

Flexible and performant utility for traversing javascript objects

251 lines (200 loc) 5.66 kB
var DEFAULT_SEPARATOR = '.'; var DEFAULT_TRAVERSAL_OPTS = { traversalType: 'depth-first', maxNodes: Number.POSITIVE_INFINITY, cycleHandling: true, maxDepth: Number.POSITIVE_INFINITY, haltOnTruthy: false, pathSeparator: DEFAULT_SEPARATOR }; var _Queue = /*#__PURE__*/function () { function _Queue() { this.head = undefined; this.tail = undefined; } var _proto = _Queue.prototype; _proto.enqueue = function enqueue(v) { if (this.tail) { this.tail = this.tail.next = { value: v }; } else { this.head = this.tail = { value: v }; } }; _proto.dequeue = function dequeue() { var previousHeadValue = this.head.value; this.head = this.head.next; if (!this.head) { this.tail = this.head; } return previousHeadValue; }; _proto.isEmpty = function isEmpty() { return !this.head; }; _proto.reset = function reset() { this.head = this.tail = undefined; }; return _Queue; }(); var _QueueToStackAdapter = /*#__PURE__*/function () { function _QueueToStackAdapter(queue) { this.queue = void 0; this.queue = queue; } var _proto2 = _QueueToStackAdapter.prototype; _proto2.push = function push(v) { this.queue.enqueue(v); }; _proto2.pop = function pop() { return this.queue.dequeue(); }; _proto2.isEmpty = function isEmpty() { return this.queue.isEmpty(); }; _proto2.reset = function reset() { return this.queue.reset(); }; return _QueueToStackAdapter; }(); var _Stack = /*#__PURE__*/function () { function _Stack() { this.tail = undefined; } var _proto = _Stack.prototype; _proto.push = function push(v) { this.tail = { value: v, prev: this.tail }; }; _proto.pop = function pop() { var node = this.tail; this.tail = this.tail.prev; return node.value; }; _proto.isEmpty = function isEmpty() { return !this.tail; }; _proto.reset = function reset() { this.tail = undefined; }; return _Stack; }(); /** Applies a given callback function to all properties of an object and its children */ var traverse = function traverse(root, callback, opts) { if (!(root instanceof Object)) { throw new Error('First argument must be an object'); } var fullOpts = Object.assign({}, DEFAULT_TRAVERSAL_OPTS, opts); fullOpts.disablePathTracking = typeof fullOpts.pathSeparator !== 'string'; var stackOrQueue; if (fullOpts.traversalType === 'depth-first') { stackOrQueue = new _Stack(); } else { stackOrQueue = new _QueueToStackAdapter(new _Queue()); } var traversalMeta = { visitedNodes: new WeakSet(), depth: 0 }; if (!fullOpts.disablePathTracking) { traversalMeta.nodePath = null; } stackOrQueue.push({ parent: null, key: null, value: root, meta: traversalMeta }); _traverse(callback, stackOrQueue, fullOpts); }; var _traverse = function _traverse(callback, stackOrQueue, opts) { /** * Using a stack instead of a queue to preserve the natural depth-first traversal order. Using a queue or traversing an array * in order would lead the depth-first to traverse the value.properties in reverse order. * Breadth-first traversal uses queues as usual. */ var newNodesToVisit; if (opts.traversalType === 'depth-first') { newNodesToVisit = new _Stack(); } else { newNodesToVisit = new _QueueToStackAdapter(new _Queue()); } var maxNodes = opts.maxNodes, cycleHandling = opts.cycleHandling, maxDepth = opts.maxDepth, haltOnTruthy = opts.haltOnTruthy, pathSeparator = opts.pathSeparator; var visitedNodeCount = 0; while (!stackOrQueue.isEmpty() && maxNodes > visitedNodeCount) { var callbackContext = stackOrQueue.pop(); var value = callbackContext.value, meta = callbackContext.meta; var visitedNodes = meta.visitedNodes; var nodeIsObject = value instanceof Object; var skipNode = cycleHandling && nodeIsObject && visitedNodes.has(value); if (skipNode) { continue; } if (callback(callbackContext) && haltOnTruthy) { break; } visitedNodeCount++; if (nodeIsObject) { visitedNodes.add(value); var depth = meta.depth, nodePath = meta.nodePath; var newDepth = depth + 1; if (newDepth > maxDepth) { continue; } newNodesToVisit.reset(); var keys = Object.keys(value); for (var i = 0; i < keys.length; i++) { var property = keys[i]; var traversalMeta = { visitedNodes: visitedNodes, depth: newDepth }; var newPath = void 0; if (!opts.disablePathTracking) { if (!nodePath) { newPath = property; } else { newPath = "" + nodePath + pathSeparator + property; } traversalMeta.nodePath = newPath; } newNodesToVisit.push({ value: value[property], meta: traversalMeta, key: property, parent: value }); } while (!newNodesToVisit.isEmpty()) { stackOrQueue.push(newNodesToVisit.pop()); } } } }; function getNodeByPath(root, path, separator) { if (separator === void 0) { separator = DEFAULT_SEPARATOR; } var node = root; var segments = path.split(separator); var index = 0; var segment = segments[index]; while (node && segment) { node = node[segment]; segment = segments[++index]; } return node; } export { _Queue, _QueueToStackAdapter, _Stack, getNodeByPath, traverse }; //# sourceMappingURL=object-traversal.esm.js.map