From 481a2099e8c6aad570efdcf46028a5e7495cc085 Mon Sep 17 00:00:00 2001 From: Mesqueeb Date: Sat, 13 Jul 2019 15:03:55 +0900 Subject: [PATCH] =?UTF-8?q?Support=20for=20(non-=20&)=20enumerable=20props?= =?UTF-8?q?=20&=20symbols=20=F0=9F=A6=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ dist/index.cjs.js | 24 +++++++++++++++++++++--- dist/index.esm.js | 24 +++++++++++++++++++++--- package.json | 2 +- src/index.ts | 23 +++++++++++++++++++++-- test/index.js | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 112 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index f92e0c8..ea831fe 100644 --- a/README.md +++ b/README.md @@ -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! diff --git a/dist/index.cjs.js b/dist/index.cjs.js index 42e4ee1..e30d8a8 100644 --- a/dist/index.cjs.js +++ b/dist/index.cjs.js @@ -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. * @@ -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; }, {}); } diff --git a/dist/index.esm.js b/dist/index.esm.js index 94d0aef..80e7667 100644 --- a/dist/index.esm.js +++ b/dist/index.esm.js @@ -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. * @@ -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; }, {}); } diff --git a/package.json b/package.json index 7274f8b..121772f 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/index.ts b/src/index.ts index 6dd29ac..279f330 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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. * @@ -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 }, {}) } diff --git a/test/index.js b/test/index.js index 9488403..4e151d7 100644 --- a/test/index.js +++ b/test/index.js @@ -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) +})