deep-copy-system
v4.0.0
Published
Deep copy anything including programmer defined classes. Create deep hybrid-objects
Downloads
14
Maintainers
Readme
deep-copy-system 4.0.0
You might want this package because it does something no one thought was possible. You can deep-copy ES5 classes and ES6 classes even when derived/inherited, and you can create deep-hybrid objects. If you are not interested in that, then blitz-copy is better. The blitz-copy and deep-copy-system packages are the most powerful and the most correct deep-copying packages on NPM as concluded by examining thirty-five other deep copying packages, none of which has fully passed this author's test suite: only one other comes close. Blitz-copy is the fastest ES6 deep-copier on NPM.
A hybrid object is a target object directly given the functionality of one or multiple source objects. It's emphasized functionality is neither borrowed nor shared as the internal states of the source objects are deep copied before transferring to the target object. That's never before been possible in JavaScript. A source and target are truly independent: neither can affect the internal state of the other unless the other allows it with a public method that modifies its own internal state.
Version 4.0.0 features a bug fix for ES6 private #-fields, and new support for deep-copying base class properties. With the latter piece in place, deep-copy-system has reached maturity.
P.S. The Tests folder, with its large number of files, may be deleted if desired.
Deep Copy Anything and Everything Including Programmer Defined Classes
Concepts, and code implementation were first published in 2019 at https://meouzer.com and only recently at https://meouzer.github.io. The sites are nascent and out of date: NPM code is better. However, these sites clearly lay out the ideas, motivations, and a great amount of deep copying groundwork.
Specification of the deepCopy(x)
function
The following are copied when found in the object stream.
- Circular and duplicate references
- Property descriptors
- Frozen, sealed, and extensible states
- Getters/setters (evaluator might be required)
- All ES6 = ECMA-2015 data types
- All Primitives
- Boolean
- Number
- String
- Date
- RegExp
- Array
- All Typed Arrays
- All Error Classes
- Map
- Set
- DataView
- ArrayBuffer
- Buffer
- WeakMaps, and WeakSets (copied as is since programmer can't read internal state)
- Promises: copied as is
- Function (evaluator might be required)
- Null objects (objects not deriving from Object.prototype)
- Secondary objects (objects created with Object.create())
- Complexity
- E.g., Sets whose members are Maps whose keys are ArrayBuffers and values are TypedArrays. The Set can also have properties that are DataViews whose properties are Maps.
- Circular and duplicate references may have ends in the members of a Set, the keys and values of a Map, or the buffers of DataViews/TypedArrays.
If you know about evaluators from meouzer.com, then you may supply an evaluator as the second parameter
as in deepCopy(x, evaluator)
. Here, x
may be more than pure data by having
sub-functions x.a.b.c...
, which in turn may be getters/setters. The evaluator is
used to copy these sub-functions into the appropriate context.
If getters/setters and functions in the object tree always rely only on their local and global variables, then an evaluator need not and should not be used.
Basic Usage
npm install deep-copy-system
const {deepCopy, deepCopyExt} = require('deep-copy-system')
x = complicated data object built from plain objects,
null objects, secondary objects, and ES6 data types
y = deepCopy(x) // y is a deep copy of x
--------------------------------------------------
Now for deepCopyExt(x, params)
y = deepCopyExt(x, {cd:true, pd:false, freeze:true,
deepFreezeCopy:true, allProperties:false} )
/* cd:true -> copy circular and duplicate references
pd:false -> don't copy property descriptors
freeze:true -> copy frozen/sealed/extensible states
(in this case redundant because deepFreezeCopy
is also true)
deepFreezeCopy:true -> make sure the deep copy is deep
frozen. Some functional programmers like their
deep-copies deep-frozen.
allProperties:false -> only copy the enumerable properties
all fields if unspecified default to false */
y will not be a deep copy of x if the fields of params
don't match the reality that is x.
--------------------------------------------------
Example
You know for sure that in the object tree of x
there are no circular references, no duplicate
references, no configurations with property
descriptors, no frozen/sealed/extensible states,
and only enumerable properties. Then setting all
params fields to false matches the reality of x.
Whence
y = deepCopyExt(x) is a deep copy of x.
deepCopy(x)
is equivalent to deepCopyExt(x, {cd:true, pd:true, freeze:true, allProperties:true})
Object.getOwnPropertyNames(x)
gives all properties of x
while Object.keys(x)
gives only the
enumerable ones.
Getters/Setters
If you want to copy getters/setters, then you must set params.pd
to true
because getters/setters
are copied by copying property descriptors. If your getter/setter is not inlined but rather created
with Object.defineProperty()
, Object.defineProperties()
, or Object.create()
where the enumerable flag is
not set to true
, then you must also set params.allProperties
to true
to ensure that properties
come from Object.getOwnPropertyNames
.
Exports of deep-copy-system
| Export | Description |
|---|---|
| deepCopyExt()
| Has options to handle or not circular/duplicate references, property descriptors, and sealed/frozen/extensible states. May choose to copy all properties or just the enumerable properties. |
| deepCopy()
| Deep copies everything. Wrapper around deepCopyExt(). |
|dcsUtil
| A plain object used to deep copy programmer defined ES5 and ES6 classes. Its properties are mostly "hidden exports" only used while evaluating with eval().|
Supports
Node.js, FireFox, Chrome, Opera, and Edge are supported. IE11 is not supported because symbols are used and IE11 does not support symbols.
The Three Dots Explanation
In the deepCopy()
functions to follow in the next two sections, three dots are displayed as in
{this:this, ...}
. If the private variables/fields of the class defined in the constructor are var a
,
let b
, const c
, and this.#d
then the three dots refer to the following.
a:a, b:b, "const c":c, "#d":this.#d
Deep Copying of Programmer Defined ES5 classes
const {dcsUtil} = require('deep-copy-system')
function Bax() // ES5 base class
{
// The important first line
if(arguments[0] === dcsUtil.defaultConstructor) return;
...
this.deepCopy = function()
{
return eval(dcsUtil.deepCopyClass)(Bax, arguments, null,
{
this:this, ...
})
}
}
function Foo(x,y) // ES5 derived class: inherits from Bax
{
// The important first five lines
if(arguments[0] === dcsUtil.defaultConstructor) {
Bax.call(this, dcsUtil.defaultConstructor);
return;
}
Bax.call(this, ...);
const superCopy = this.deepCopy
...
(function nestedFunction(w,x)
{
var/const/let y;
/* The cardinal rule also applicable to ES6:
--------------------------------------------------------
No methods/functions in the extened-object-tree of Foo
are to be defined inside any nested function, unless
they don't depend on the local w,x,y. But then why
define in a nested function in the first place? */
// Changes to pure data variables of the class allowed.
})(5,7);
...
this.deepCopy = function()
{
return eval(dcsUtil.deepCopyClass)(Foo, arguments, superCopy,
{
this:this, ...
},
new Set(["baxMethod_1", "baxMethod_2"])
)
}
// The Set includes all the names of the methods of Bax that
// are not names of methods of Foo. So for example,
// "baxMethod_1" is the name of a Bax method, but not the name
// of any Foo method. The Set need not be specified if there are
// no such names.
}
const x = new Foo(1,2);
const y = x.deepCopy(); // y is a deep copy of x
const z = y.deepCopy(); // z is a deep copy of y
Deep Copying of Programmer Defined ES6 Classes
const {dcsUtil} = require('deep-copy-system')
class Bax // ES6 base class
{
...
constructor()
{
// The important first line
if(arguments[0] === dcsUtil.defaultConstructor) return;
...
this.deepCopy = function()
{
return eval(dcsUtil.deepCopyClass)(Bax, arguments, null,
{
this:this, ...
})
}
}
}
class Foo extends Bax // ES6 derived class
{
...
constructor()
{
// The important first lines
if(arguments[0] === dcsUtil.defaultConstructor) {
super(dcsUtil.defaultConstructor);
return;
}
super(...)
const superCopy = this.deepCopy
...
this.deepCopy = function()
{
return eval(dcsUtil.deepCopyClass)(Foo, arguments, superCopy,
{
this:this, ...
},
new Set(["baxMethod_1", "baxMethod_2"])
)
}
// The Set includes all the names of the methods of Bax that
// are not names of methods of Foo. So for example,
// "baxMethod_1" is the name of a Bax method, but not the name
// of any Foo method. The Set need not be specified if there are
// no such names.
}
}
Hybrid Objects of ES5 Class Instances
Let x
be an instance of an ES5 Foo class.
The function call x.deepCopy(y)
creates a hybrid object. In addition
to its own functionality, y
now has the functionality of x
.
x
and y
are independent in that their internal states
do not share any resources because any such resources were deep copied from x
before transferring to y
.
const y = new Bar()
const x = new Foo()
// The hybridization code
x.deepCopy(y)
// y is now a hybrid object, being a class instance of Bar,
// but also with the functionality of x.
Foo could be an ES6 class as long as it has no private fields. A private field simply can not be copied over to an object that is not itself a class instance of Foo.
Create Hybrid Object Derived from two Objects
const x = new Foo()
const y = new Bar()
// Create Hybrid Object derived from x and y
const z = x.deepCopy(y.deepCopy())
// z has the functionality of x and y.
// z is also an instance of Bar.
You can continue on. For example, w.deepCopy(x.deepCopy(y.deepCopy()))
is a three-fold hybrid: a hybrid of w
, x
, and y
. w.deepCopy(x.deepCopy(y.deepCopy(z)))
makes z a four-fold hybrid.
Name Conflicts in Hybridization
You can think of hybridization as merging in the same manner that Object.assign
does.
In both cases, naming conflicts are fatal. If x
and y
have property names in common,
there is no creating an x-y
hybrid as either the x-part or y-part will be broken.
The best one can do is
const z = ...
z.x = deepCopy(x)
z.y = deepCopy(y)
Bonus Section for getting this far
What if you have a source object without a deepCopy(target)
hybridization method?
Well, see the Supplement.md file.
Testing
In the node-modules/deep-copy-system folder, there is a Test folder with twelve test and test-supporting files. Each test file uses weirdly complicated constructs to test deep copying to the max. If you wish, the Test folder may be deleted.
Testing is done with files stolen from the deep-copy-diagnostics package, which catches every possible way in which two objects may not be deep copies of each other. Both blitz-copy and deep-copy-system take into account internal prototypes, getters/setters, property descriptors, circular/duplicate references, and frozen/sealed/extensible states: so deep-copy-diagnostics does too as it was designed with these two packages in mind.