Skip to content

Commit

Permalink
Support for (non- &) enumerable props & symbols 🦄
Browse files Browse the repository at this point in the history
  • Loading branch information
mesqueeb committed Jul 13, 2019
1 parent 3e207d2 commit 481a209
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 9 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ I was looking for:
- has to be fast!
- props must lose any reference to original object
- works with arrays and objects in arrays!
- supports symbols
- supports enumerable & nonenumerable props
- **does not break special class instances** ‼️

This last one is crucial! So many libraries use custom classes that create objects with special prototypes, and such objects all break when trying to copy them inproperly. So we gotta be careful!
Expand Down
24 changes: 21 additions & 3 deletions dist/index.cjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

var isWhat = require('is-what');

function assignProp(carry, key, newVal, originalObject) {
var propType = originalObject.propertyIsEnumerable(key)
? 'enumerable'
: 'nonenumerable';
if (propType === 'enumerable')
carry[key] = newVal;
if (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.
*
Expand All @@ -14,10 +29,13 @@ function copy(target) {
return target.map(function (i) { return copy(i); });
if (!isWhat.isPlainObject(target))
return target;
return Object.keys(target)
.reduce(function (carry, key) {
var props = Object.getOwnPropertyNames(target);
var symbols = Object.getOwnPropertySymbols(target);
return props.concat(symbols).reduce(function (carry, key) {
// @ts-ignore
var val = target[key];
carry[key] = copy(val);
var newVal = copy(val);
assignProp(carry, key, newVal, target);
return carry;
}, {});
}
Expand Down
24 changes: 21 additions & 3 deletions dist/index.esm.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
import { isArray, isPlainObject } from 'is-what';

function assignProp(carry, key, newVal, originalObject) {
var propType = originalObject.propertyIsEnumerable(key)
? 'enumerable'
: 'nonenumerable';
if (propType === 'enumerable')
carry[key] = newVal;
if (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.
*
Expand All @@ -12,10 +27,13 @@ function copy(target) {
return target.map(function (i) { return copy(i); });
if (!isPlainObject(target))
return target;
return Object.keys(target)
.reduce(function (carry, key) {
var props = Object.getOwnPropertyNames(target);
var symbols = Object.getOwnPropertySymbols(target);
return props.concat(symbols).reduce(function (carry, key) {
// @ts-ignore
var val = target[key];
carry[key] = copy(val);
var newVal = copy(val);
assignProp(carry, key, newVal, target);
return carry;
}, {});
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "copy-anything",
"version": "1.2.4",
"version": "1.3.0",
"description": "An optimised way to copy'ing an object. A small and simple integration",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
Expand Down
23 changes: 21 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
import { isPlainObject, isArray } from 'is-what'

function assignProp (carry, key, newVal, originalObject) {
const propType = originalObject.propertyIsEnumerable(key)
? 'enumerable'
: 'nonenumerable'
if (propType === 'enumerable') carry[key] = newVal
if (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.
*
Expand All @@ -10,10 +25,14 @@ import { isPlainObject, isArray } from 'is-what'
export default function copy (target: any): any {
if (isArray(target)) return target.map(i => copy(i))
if (!isPlainObject(target)) return target
return Object.keys(target)
const props = Object.getOwnPropertyNames(target)
const symbols = Object.getOwnPropertySymbols(target)
return [...props, ...symbols]
.reduce((carry, key) => {
// @ts-ignore
const val = target[key]
carry[key] = copy(val)
const newVal = copy(val)
assignProp(carry, key, newVal, target)
return carry
}, {})
}
46 changes: 46 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,49 @@ test('special objects', t => {
res = copy(target)
t.deepEqual(res, target)
})

test('symbols as keys', t => {
let res, target
const mySymbol = Symbol('mySymbol')
target = { value: 42, [mySymbol]: 'hello' }
res = copy(target)
// change original
target.value = 1
target[mySymbol] = 2
t.is(res.value, 42)
t.is(res[mySymbol], 'hello')
t.is(target.value, 1)
t.is(target[mySymbol], 2)
})

test('nonenumerable keys', t => {
let target, res
const mySymbol = Symbol('mySymbol')
target = { value: 42 }
Object.defineProperty(target, 'id', {
value: 1,
writable: true,
enumerable: false,
configurable: true
})
Object.defineProperty(target, mySymbol, {
value: 'original',
writable: true,
enumerable: false,
configurable: true
})
res = copy(target)
// change original
target.id = 100
target[mySymbol] = 'new'
target.value = 300
t.is(res.value, 42)
t.is(res.id, 1)
t.is(res[mySymbol], 'original')
t.is(Object.keys(res).length, 1)
t.true(Object.keys(res).includes('value'))
t.is(target.id, 100)
t.is(target[mySymbol], 'new')
t.is(target.value, 300)
t.is(Object.keys(target).length, 1)
})

0 comments on commit 481a209

Please sign in to comment.