Skip to content
forked from fossabot/clone

Fast, deep, scalable object cloning for JavaScript

License

Notifications You must be signed in to change notification settings

hartl3y94/clone

 
 

Repository files navigation

@golden-ants/clone

Node.js CI npm version FOSSA Status install size License: MIT

A new approach to deep cloning. A simple library with no external dependencies gives you the opportunity to customize the cloning strategy.

Table of Contents

Installing

Using npm:

npm install --save @golden-ants/clone

Using yarn:

yarn add @golden-ants/clone

Usage

Create an instance of the Clone class by passing switchers to it.

import { Clone, Switcher } from '@golden-ants/clone'

const switchers: Switcher[] = [
  {
    // all primitive types
    if: (value) => value === null || typeof value !== 'object',

    // just return the same value
    handler: (value) => value,
  },
  {
    // all non-primitive types
    if: (value) => value !== null && typeof value === 'object',
    then: [
      {
        if: (value) => value instanceof Array,

        // copy a nested array using the same switchers
        handler: (value, clone) => clone.it([], value as object),
      },
      {
        if: (value) => value instanceof Object,

        // copy a nested object using the same switchers
        handler: (value, clone) => clone.it({}, value as object),
      },
    ],
  },
]

export const deepClone = new Clone(switchers)

Note: The alternative is to use functional utilities

Declarative solution:

const switchers: Switcher[] = [
  {
    if: not(isObject),
    handler: (value) => value,
  },
  {
    if: isObject,
    then: [
      {
        if: instanceOf(Array),
        handler: retryFor(() => []),
      },
      {
        if: instanceOf(Object),
        handler: retryFor(() => ({})),
      },
    ],
  },
]

Calling the created deepClone for deep recursive copying of objects or arrays.

Note: The target of copying can be an array or an object, but array elements or object property keys can only be those types that meet the conditions of the switchers.

import { deepClone } from ...

// array (object) of any nesting level
const array = [1, [2], { a: [4, 5] }];

const copied = deepClone.it([], array);

Switcher

Switchers are needed to analyze the contents of objects and apply unique ways of processing values.

Each Switcher contains an if property that accepts a predicate function, the second property can be either a handler or an array of nested switches.

const switcher: Switcher[] = [
  {
    // all primitives except null
    if: (value) => typeof value !== 'object',

    /**if the condition is satisfied and the "then" property
     * is defined, the value will be passed from top to bottom
     * until the result of the comparison is true, otherwise
     * an exception will be thrown */
    then: [
      {
        if: (value) => typeof value !== 'string',
        handler: (value) => (value as string).toUpperCase(),
      },
      {
        // We repeated the upper condition to avoid an exception
        if: (value) => typeof value !== 'object',
        handler: (value) => value,
      },
    ],
  },
  /**If an array or object contains a non-primitive
   * value or null, and the corresponding switch is
   * not declared, an error will be thrown */
  //{
  //  if: (value) => typeof value === "object",
  //  handler: (value) => ...
  //}
]

Warning: If you define the then and handler properties in an Switcher, the consequences will be unpredictable

Clone

An instance of the Clone class contains a single public member - the it method.

Warning: Method arguments can only be non-null objects.

Cloning occurs superficially, a handler is applied to each property of the source, the result of which is assigned to the target. But thanks to the hook, you can call the cloning again, but for a nested object.

const clone = new Clone(...)

const target = {}
const source = { foo: "bar" }

const copied = clone.it(target, source)

copied === target // true
copied === source // false

// clone.it(317, source) // runtime error

And so the handler can cause cloning for a nested object.

...{
  if: isObject,
  /**value: object property value
   * clone: an instance of the Clone class to which these switchers will be passed*/
  handler: (value, clone) => clone.it({}, value),
}...

Functional utilities

instanceOf

Higher order function.

class MyClass {}

const isMyClass = instanceOf(MyClass)

const mc = new MyClass()

isMyClass(mc) // true

isObject

Checks whether the value is not primitive.

isObject('clone') // false
isObject(null) // false
isObject({}) // true
isObject(Object.create(null)) // true

retryFor

A higher-order function accepting a supplier.

...{
  if: (value) => value instanceof Array,
  handler: (value, clone) => clone.it([], value as object),
}...

It's clearer this way.

...{
  if: instanceOf(Array),
  handler: retryFor(() => []),
}...

not

A higher-order function, returns a function through the result will be inverted.

const isPrimitive = not(isObject)

isPrimitive(1)) //true
isPrimitive(null)) //true

Examples

Shallow copy

import { Clone, Switcher } from '@golden-ants/clone'

const switchers: Switcher[] = [
  {
    if: () => true,
    handler: (value) => value,
  },
]

const shallowCopy = new Clone(switchers)

const source = {
  foo: {
    bar: 'abc',
  },
}

const target: Record<string, unknown> = {}

shallowCopy.it(target, source)

console.log(target.foo === source.foo) // true

Deep copy

import {
  Clone,
  instanceOf,
  isObject,
  not,
  retryFor,
  Switcher,
} from '@golden-ants/clone'

const switchers: Switcher[] = [
  {
    if: not(isObject),
    handler: (value) => value,
  },
  {
    if: isObject,
    then: [
      {
        if: instanceOf(Array),
        handler: retryFor(() => []),
      },
      {
        if: instanceOf(Object),
        handler: retryFor(() => ({})),
      },
    ],
  },
]

const deepCopy = new Clone(switchers)

const source = {
  foo: {
    bar: 'abc',
  },
}

const target: Record<string, unknown> = {}

deepCopy.it(target, source)

console.log(target.foo === source.foo) // false

Custom copy

import {
  Clone,
  instanceOf,
  isObject,
  not,
  retryFor,
  Switcher,
} from '@golden-ants/clone'

class A {
  private storage: string
  constructor(storage: string) {
    this.storage = storage
  }
  clone = () => new A(this.storage + ' clone A')
  get = () => this.storage
}

const switchers: Switcher[] = [
  {
    if: not(isObject),
    handler: (value) => value,
  },
  {
    if: isObject,
    then: [
      {
        if: instanceOf(A),
        handler: (value) => (value as A).clone(),
      },
      {
        if: instanceOf(Array),
        handler: retryFor(() => []),
      },
      {
        if: instanceOf(Object),
        handler: retryFor(() => ({})),
      },
    ],
  },
]

const customCopy = new Clone(switchers)

const source = [new A('foo'), new A('bar')]

const target: A[] = []

customCopy.it(target, source)

target[0].get() // 'foo clone A'
target[1].get() // 'bar clone A'

License

FOSSA Status

About

Fast, deep, scalable object cloning for JavaScript

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks

Packages

No packages published

Languages

  • TypeScript 78.0%
  • JavaScript 17.8%
  • Shell 4.2%