deepdash
Version:
➔ 𝐃eep standalone lib / 𝐋odash extension: ✓ eachDeep ✓ filterDeep ✓ mapDeep ✓ reduceDeep ✓ pickDeep ✓ omitDeep ✓ keysDeep ✓ index ✓ condenseDeep ⋮ Parents stack ⋮ Circular check ⋮ Leaves only mode ⋮ Children mode ⋮ cherry-pick ⋮ esm
356 lines (319 loc) • 9.47 kB
JavaScript
import getPathToString from './../getPathToString.js';
import isObject from './isObject.js';
var rxVarName = /^[a-zA-Z_$]+([\w_$]*)$/;
var rxQuot = /"/g;
const has = Object.prototype.hasOwnProperty;
export default function getIterate(_) {
const pathToString = getPathToString(_);
function iterate(item) {
const { options, obj, callback } = item;
options.pathFormatArray = options.pathFormat == 'array';
item.depth = 0;
let broken = false;
const breakIt = () => {
broken = true;
return false;
};
while (item) {
if (broken) break;
if (!item.inited) {
item.inited = true;
item.info = describeValue(item.value, options.ownPropertiesOnly);
if (options.checkCircular) {
item.circularParentIndex = -1;
item.circularParent = null;
item.isCircular = false;
if (item.info.isObject && !item.info.isEmpty) {
let parent = item.parent;
while (parent) {
if (parent.value === item.value) {
item.isCircular = true;
item.circularParent = parent;
item.circularParentIndex = item.depth - parent.depth - 1;
break;
}
parent = parent.parent;
}
}
}
item.children = [];
if (options.childrenPath) {
options.childrenPath.forEach((cp, i) => {
const children = _.get(item.value, cp);
const info = describeValue(children, options.ownPropertiesOnly);
if (!info.isEmpty) {
item.children.push([
cp,
options.strChildrenPath[i],
children,
info,
]);
}
});
}
item.isLeaf =
item.isCircular ||
(options.childrenPath !== undefined && !item.children.length) ||
!item.info.isObject ||
item.info.isEmpty;
item.needCallback =
(item.depth || options.includeRoot) &&
(!options.leavesOnly || item.isLeaf);
if (item.needCallback) {
const contextReader = new ContextReader(obj, options, breakIt);
contextReader.setItem(item, false);
try {
item.res = callback(
item.value,
item.key,
item.parent && item.parent.value,
contextReader
);
} catch (err) {
if (err.message) {
err.message +=
'\ncallback failed before deep iterate at:\n' +
pathToString(item.path);
}
throw err;
}
}
if (broken) {
break;
}
if (item.res !== false) {
if (!broken && !item.isCircular && item.info.isObject) {
if (
options.childrenPath !== undefined &&
(item.depth || !options.rootIsChildren)
) {
item.childrenItems = [];
if (item.children.length) {
item.children.forEach(([cp, scp, children, info]) => {
item.childrenItems = [
...item.childrenItems,
...(info.isArray
? getElements(item, children, options, cp, scp)
: getOwnChildren(item, children, options, cp, scp)),
];
});
}
} else {
item.childrenItems = item.info.isArray
? getElements(item, item.value, options, [], '')
: getOwnChildren(item, item.value, options, [], '');
}
}
}
item.currentChildIndex = -1;
}
if (
item.childrenItems &&
item.currentChildIndex < item.childrenItems.length - 1
) {
item.currentChildIndex++;
item.childrenItems[item.currentChildIndex].parentItem = item;
item = item.childrenItems[item.currentChildIndex];
continue;
}
if (item.needCallback && options.callbackAfterIterate) {
const contextReader = new ContextReader(obj, options, breakIt);
contextReader.setItem(item, true);
try {
callback(
item.value,
item.key,
item.parent && item.parent.value,
contextReader
);
} catch (err) {
if (err.message) {
err.message +=
'\ncallback failed after deep iterate at:\n' +
pathToString(item.path);
}
throw err;
}
}
item = item.parentItem;
}
}
return iterate;
function getElements(item, children, options, childrenPath, strChildrenPath) {
let strChildPathPrefix;
if (!options.pathFormatArray) {
strChildPathPrefix = item.strPath || '';
if (
strChildrenPath &&
strChildPathPrefix &&
!strChildrenPath.startsWith('[')
) {
strChildPathPrefix += '.';
}
strChildPathPrefix += strChildrenPath || '';
}
const res = [];
for (var i = 0; i < children.length; i++) {
const val = children[i];
if (val === undefined && !(i in children)) {
continue;
}
let strChildPath;
const pathFormatString = !options.pathFormatArray;
if (pathFormatString) {
strChildPath = `${strChildPathPrefix}[${i}]`;
}
res.push({
value: val,
key: i + '',
path: [...(item.path || []), ...childrenPath, i + ''],
strPath: strChildPath,
depth: item.depth + 1,
parent: {
value: item.value,
key: item.key,
path: pathFormatString ? item.strPath : item.path,
parent: item.parent,
depth: item.depth,
info: item.info,
},
childrenPath: (childrenPath.length && childrenPath) || undefined,
strChildrenPath: strChildrenPath || undefined,
});
}
return res;
}
function getOwnChildren(
item,
children,
options,
childrenPath,
strChildrenPath
) {
let strChildPathPrefix;
if (!options.pathFormatArray) {
strChildPathPrefix = item.strPath || '';
if (
strChildrenPath &&
strChildPathPrefix &&
!strChildrenPath.startsWith('[')
) {
strChildPathPrefix += '.';
}
strChildPathPrefix += strChildrenPath || '';
}
const res = [];
const pathFormatString = !options.pathFormatArray;
for (var childKey in children) {
if (options.ownPropertiesOnly && !has.call(children, childKey)) {
continue;
}
let strChildPath;
if (pathFormatString) {
if (rxVarName.test(childKey)) {
if (strChildPathPrefix) {
strChildPath = `${strChildPathPrefix}.${childKey}`;
} else {
strChildPath = `${childKey}`;
}
} else {
strChildPath = `${strChildPathPrefix}["${childKey.replace(
rxQuot,
'\\"'
)}"]`;
}
}
res.push({
value: children[childKey],
key: childKey,
path: [...(item.path || []), ...childrenPath, childKey],
strPath: strChildPath,
depth: item.depth + 1,
parent: {
value: item.value,
key: item.key,
path: pathFormatString ? item.strPath : item.path,
parent: item.parent,
depth: item.depth,
info: item.info,
},
childrenPath: (childrenPath.length && childrenPath) || undefined,
strChildrenPath: strChildrenPath || undefined,
});
}
return res;
}
}
class ContextReader {
constructor(obj, options, breakIt) {
this.obj = obj;
this._options = options;
this['break'] = breakIt;
}
setItem(item, afterIterate) {
this._item = item;
this.afterIterate = afterIterate;
}
get path() {
return this._options.pathFormatArray ? this._item.path : this._item.strPath;
}
get parent() {
return this._item.parent;
}
get parents() {
if (!this._item._parents) {
this._item._parents = [];
let curParent = this._item.parent;
while (curParent) {
this._item._parents[curParent.depth] = curParent;
curParent = curParent.parent;
}
}
return this._item._parents;
}
get depth() {
return this._item.depth;
}
get isLeaf() {
return this._item.isLeaf;
}
get isCircular() {
return this._item.isCircular;
}
get circularParentIndex() {
return this._item.circularParentIndex;
}
get circularParent() {
return this._item.circularParent;
}
get childrenPath() {
return (
(this._options.childrenPath !== undefined &&
(this._options.pathFormatArray
? this._item.childrenPath
: this._item.strChildrenPath)) ||
undefined
);
}
get info() {
return this._item.info;
}
}
function isObjectEmpty(value, ownPropertiesOnly) {
for (var key in value) {
if (!ownPropertiesOnly || has.call(value, key)) {
return false;
}
}
return true;
}
function describeValue(value, ownPropertiesOnly) {
const res = { isObject: isObject(value) };
res.isArray = res.isObject && Array.isArray(value);
res.isEmpty = res.isArray
? !value.length
: res.isObject
? isObjectEmpty(value, ownPropertiesOnly)
: true;
return res;
}