'use strict'; const isObject = value => typeof value === 'object' && value !== null; const mapObjectSkip = Symbol('skip'); // Customized for this use-case const isObjectCustom = value => isObject(value) && !(value instanceof RegExp) && !(value instanceof Error) && !(value instanceof Date); const mapObject = (object, mapper, options, isSeen = new WeakMap()) => { options = { deep: false, target: {}, ...options }; if (isSeen.has(object)) { return isSeen.get(object); } isSeen.set(object, options.target); const {target} = options; delete options.target; const mapArray = array => array.map(element => isObjectCustom(element) ? mapObject(element, mapper, options, isSeen) : element); if (Array.isArray(object)) { return mapArray(object); } for (const [key, value] of Object.entries(object)) { const mapResult = mapper(key, value, object); if (mapResult === mapObjectSkip) { continue; } let [newKey, newValue, {shouldRecurse = true} = {}] = mapResult; // Drop `__proto__` keys. if (newKey === '__proto__') { continue; } if (options.deep && shouldRecurse && isObjectCustom(newValue)) { newValue = Array.isArray(newValue) ? mapArray(newValue) : mapObject(newValue, mapper, options, isSeen); } target[newKey] = newValue; } return target; }; module.exports = (object, mapper, options) => { if (!isObject(object)) { throw new TypeError(`Expected an object, got \`${object}\` (${typeof object})`); } return mapObject(object, mapper, options); }; module.exports.mapObjectSkip = mapObjectSkip;