decycle vs cloneWithoutCircular

Benchmark created on


Setup

let tmpA = {test: 1};
tmpA.ref = tmpA;

function clonedeep(obj, clonedObjects = new WeakMap()) {
  // Handle primitive types and null
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  // Handle circular references
  if (clonedObjects.has(obj)) {
    return clonedObjects.get(obj);
  }

  let clone;

  // Handle Arrays
  if (Array.isArray(obj)) {
    clone = [];
    clonedObjects.set(obj, clone); // Store the clone before recursing
    for (let i = 0; i < obj.length; i++) {
      clone[i] = clonedeep(obj[i], clonedObjects);
    }
    return clone;
  }

  // Handle Objects
  if (typeof obj === 'object') {
    clone = {};
    clonedObjects.set(obj, clone); // Store the clone before recursing
    for (const key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        clone[key] = clonedeep(obj[key], clonedObjects);
      }
    }
    return clone;
  }

  // Handle other types like Date, RegExp, etc. (basic handling)
  // For more robust handling, consider specific checks for each type.
  if (obj instanceof Date) {
    return new Date(obj.getTime());
  }
  if (obj instanceof RegExp) {
    return new RegExp(obj.source, obj.flags);
  }

  // Fallback for unhandled types
  return obj;
}

const decycle = (object) => {
  let objects = new WeakMap(); // object to path mappings
  let old_path; // The path of an earlier occurance of value
  let nu; // The new object or array

  return (function _decycle(value, path) {
    if (
      typeof value === 'object' &&
      value !== null &&
      !(value instanceof Boolean) &&
      !(value instanceof Date) &&
      !(value instanceof Number) &&
      !(value instanceof RegExp) &&
      !(value instanceof String)
    ) {
      old_path = objects.get(value);
      if (old_path !== undefined) {
        return { $ref: old_path };
      }
      objects.set(value, path);
      if (Array.isArray(value)) {
        nu = [];
        value.forEach(function (element, i) {
          nu[i] = _decycle(element, path + '[' + i + ']');
        });
      } else {
        nu = {};
        Object.keys(value).forEach(function (name) {
          nu[name] = _decycle(
            value[name],
            path + '[' + JSON.stringify(name) + ']',
          );
        });
      }
      return nu;
    }
    return value;
  })(object, '$');
};



function cloneWithoutCircular (obj) {
  const objClone = clonedeep(obj);
  const seen = new Set;
  const recurse = objClone => {
    seen.add(objClone, true);
    for (let [k, v] of Object.entries(objClone)) {
      if (typeof v !== "object") continue;
      if (seen.has (v)) delete objClone[k];
      else recurse (v);
    }
  }
  recurse(objClone);
  return objClone;
}

Test runner

Ready to run.

Testing in
TestOps/sec
decycle
const tmpB = decycle(tmpA);
console.log(tmpB);
ready
cloneWithoutCircular
const tmpB = cloneWithoutCircular(tmpA);
console.log(tmpB);
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.