| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152 |
- import { isArray, isPlainObject } from 'is-what';
- function assignProp(carry, key, newVal, originalObject, includeNonenumerable) {
- const propType = {}.propertyIsEnumerable.call(originalObject, key)
- ? 'enumerable'
- : 'nonenumerable';
- if (propType === 'enumerable')
- carry[key] = newVal;
- if (includeNonenumerable && propType === 'nonenumerable') {
- Object.defineProperty(carry, key, {
- value: newVal,
- enumerable: false,
- writable: true,
- configurable: true,
- });
- }
- }
- /**
- * Copy (clone) an object and all its props recursively to get rid of any prop referenced of the
- * original object. Arrays are also cloned, however objects inside arrays are still linked.
- *
- * @param target Target can be anything
- * @param [options={}] See type {@link Options} for more details.
- *
- * - `{ props: ['key1'] }` will only copy the `key1` property. When using this you will need to cast
- * the return type manually (in order to keep the TS implementation in here simple I didn't
- * built a complex auto resolved type for those few cases people want to use this option)
- * - `{ nonenumerable: true }` will copy all non-enumerable properties. Default is `{}`
- *
- * @returns The target with replaced values
- */
- export function copy(target, options = {}) {
- if (isArray(target)) {
- return target.map((item) => copy(item, options));
- }
- if (!isPlainObject(target)) {
- return target;
- }
- const props = Object.getOwnPropertyNames(target);
- const symbols = Object.getOwnPropertySymbols(target);
- return [...props, ...symbols].reduce((carry, key) => {
- // Skip __proto__ properties to prevent prototype pollution
- if (key === '__proto__')
- return carry;
- if (isArray(options.props) && !options.props.includes(key)) {
- return carry;
- }
- const val = target[key];
- const newVal = copy(val, options);
- assignProp(carry, key, newVal, target, options.nonenumerable);
- return carry;
- }, {});
- }
|