const lodashGet = require('lodash.get'); const { setProp, unsetProp, flattenReducer } = require('../utils'); function getUnwindablePaths(obj, currentPath) { return Object.keys(obj).reduce((unwindablePaths, key) => { const newPath = currentPath ? `${currentPath}.${key}` : key; const value = obj[key]; if (typeof value === 'object' && value !== null && !Array.isArray(value) && Object.prototype.toString.call(value.toJSON) !== '[object Function]' && Object.keys(value).length) { unwindablePaths = unwindablePaths.concat(getUnwindablePaths(value, newPath)); } else if (Array.isArray(value)) { unwindablePaths.push(newPath); unwindablePaths = unwindablePaths.concat(value .map(arrObj => getUnwindablePaths(arrObj, newPath)) .reduce(flattenReducer, []) .filter((item, index, arr) => arr.indexOf(item) !== index)); } return unwindablePaths; }, []); } /** * Performs the unwind recursively in specified sequence * * @param {String[]} unwindPaths The paths as strings to be used to deconstruct the array * @returns {Object => Array} Array of objects containing all rows after unwind of chosen paths */ function unwind({ paths = undefined, blankOut = false } = {}) { function unwindReducer(rows, unwindPath) { return rows .map(row => { const unwindArray = lodashGet(row, unwindPath); if (!Array.isArray(unwindArray)) { return row; } if (!unwindArray.length) { return unsetProp(row, unwindPath); } return unwindArray.map((unwindRow, index) => { const clonedRow = (blankOut && index > 0) ? {} : row; return setProp(clonedRow, unwindPath, unwindRow); }); }) .reduce(flattenReducer, []); } paths = Array.isArray(paths) ? paths : (paths ? [paths] : undefined); return dataRow => (paths || getUnwindablePaths(dataRow)).reduce(unwindReducer, [dataRow]); } module.exports = unwind;