object-traversal
Version:
Flexible and performant utility for traversing javascript objects
251 lines (200 loc) • 5.66 kB
JavaScript
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