68 lines
1.5 KiB
JavaScript
68 lines
1.5 KiB
JavaScript
'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;
|