deep-copy-system

4.0.0 • Public • Published

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.

Versions

Current Tags

  • Version
    Downloads (Last 7 Days)
    • Tag
  • 4.0.0
    4
    • latest

Version History

Package Sidebar

Install

npm i deep-copy-system

Weekly Downloads

4

Version

4.0.0

License

ISC

Unpacked Size

146 kB

Total Files

23

Last publish

Collaborators

  • markw9