cloner 1.0.1 copy "cloner: ^1.0.1" to clipboard
cloner: ^1.0.1 copied to clipboard

Utilities for deep cloning collections and custom types with flexible extension points and optional circular reference detection.

Cloner #

Deep cloning utilities for Dart collections and custom types.

Latest Release pipeline status

BSD 3-Clause License Dart 3.9.2

Features #

  • Element/value-wise deep cloning for List, Set, and Map
  • ICloneable interface for custom types with deep-clone support
  • Collection extensions providing clone() and cloneDynamic() methods
  • MapClone wrapper for typed nested map cloning
  • Pluggable cloner architecture: swap or customize the global cloner
  • Optional circular reference detection (opt-in)
  • Preserves concrete collection types (LinkedHashMap, HashMap, etc.)

Usage #

Basic Import #

import 'package:cloner/cloner.dart';

Or import only what you need:

import 'package:cloner/core.dart';       // Cloner class + interfaces
import 'package:cloner/extensions.dart'; // Collection extensions + MapClone

Implementing ICloneable #

Define custom types that support deep cloning:

class Address implements ICloneable<Address> {
  String street;
  int number;

  Address(this.street, this.number);

  @override
  Address clone() => Address(street, number);
}

class Person implements ICloneable<Person> {
  String name;
  Address address;

  Person(this.name, this.address);

  @override
  Person clone() => Person(name, address.clone());
}

Cloning #

The Cloner facade provides a pluggable, global cloner reference and supports custom options.

Basic deep cloning

final list = [1, 2, 3];
final cloned = Cloner.instance(doTypedClone: true).cloneList(list);
cloned[0] = 99;
print(list);   // [1, 2, 3]
print(cloned); // [99, 2, 3]

Swapping the global cloner

You can replace the global cloner for all subsequent operations:

Cloner.reference = MyCustomCloner();

Collection Extensions #

Use clone() for typed cloning or cloneDynamic() for untyped cloning. Both respect the current Cloner.reference and accept an optional doCircRefCheck parameter.

// List
final numbers = [1, 2, 3];
final clonedNumbers = numbers.clone(); // List<int>

// Set
final tags = {'a', 'b', 'c'};
final clonedTags = tags.clone(); // Set<String>

// Map
final scores = {'alice': 100, 'bob': 95};
final clonedScores = scores.clone(); // Map<String, int>

// Dynamic clone for heterogeneous collections
final mixed = [1, 'two', {'key': 'value'}];
final clonedMixed = mixed.cloneDynamic(); // List<dynamic>

// Enable circular reference detection (optional)
final circSafe = numbers.clone(doCircRefCheck: true);

Nested Typed Maps #

Use MapClone when you need typed deep cloning of nested maps:

final config = MapClone<String, MapClone<String, int>>.ofMap({
  'limits': MapClone.ofMap({'maxRetries': 3, 'timeout': 30}),
});

final cloned = config.clone();
cloned['limits']!['maxRetries'] = 10;

print(config['limits']!['maxRetries']); // 3 (unchanged)
print(cloned['limits']!['maxRetries']); // 10

MapClone also provides copy() for shallow copies:

final original = MapClone<String, List<int>>();
original['data'] = [1, 2, 3];

final shallow = original.copy();  // Lists are shared
final deep = original.clone();    // Lists are cloned

Circular Reference Detection #

Note

Enabling circular reference detection incurs a performance cost and is recommended only for debugging or when cycles are possible.

By default, circular reference detection is off. To enable it (and throw CircularReferenceCloneException on cycles):

final cyclic = [];
cyclic.add(cyclic);

try {
  final clone = Cloner.instance(doCircRefCheck: true).cloneValue(cyclic);
} on CircularReferenceCloneException catch (exc) {
  print('Cycle detected: $exc');
}

[!TIP] You can also enable it via collection extensions:

final clone = myList.clone(doCircRefCheck: true);

Worth Noting #

Typed vs Dynamic Cloning #

  • Typed cloning (clone()) preserves generic types but throws UnsupportedTypedCloneException if a nested plain Map is encountered. Wrap nested maps with MapClone for typed cloning.

  • Dynamic cloning (cloneDynamic()) returns dynamic element/value types and handles any nested structure including plain maps.

Concrete Type Preservation #

When cloning collections, BaseCloner preserves the concrete type of built-in Dart collections. For example, if you clone a LinkedHashMap, HashSet, or an UnmodifiableListView, the cloned result will be of the same type, not just a generic Map, Set, or List. This ensures that collection-specific behaviors and ordering are retained in the clone.

Important

Custom collections and SplayTreeMap, SplayTreeSet, or any are not automatically supported for type-preserving deep cloning. To ensure correct cloning of these types, implement the ICloneable interface for your collection, or accept that they will be cloned as a default Map, Set, or List (losing their specific behaviors).

Benchmarks #

CountedCloner and HashedCloner both extend BaseCloner. While they may introduce some overhead compared to the default, they provide flexibility for different performance scenarios.

  • JIT Compilation:
    • Typed collections: CountedCloner
    • Dynamic collections: HashedCloner
  • AOT Compilation:
    • Lists/Sets: HashedCloner
    • Maps CountedCloner

For most use cases, the default cloner is sufficient. However, if you need to squeeze out extra performance in specific environments (JIT vs AOT, or for certain collection types), consider swapping in the recommended cloner as shown above.

Note

Detailed benchmark results available at doc/benchmarks

[The fastest Cloner implementation benchmark table]

|- More Charts! -|

JIT/AOT

[Cloner JIT benchmark chart] [Cloner AOT benchmark chart]

Cloner Matrix

[Cloner implementation benchmark matrix table]

License #

BSD 3-Clause License
This work is licensed under the BSD 3-Clause License.

0
likes
160
points
44
downloads

Publisher

unverified uploader

Weekly Downloads

Utilities for deep cloning collections and custom types with flexible extension points and optional circular reference detection.

Repository (GitLab)

Documentation

API reference

License

BSD-3-Clause (license)

More

Packages that depend on cloner