cloner 1.0.0
cloner: ^1.0.0 copied to clipboard
Utilities for producing deep clones.
Cloner #
Deep cloning utilities for Dart collections and custom types.
Features #
- Element/value-wise deep cloning for
List,Set, andMap ICloneableinterface for custom types with deep-clone support- Collection extensions providing
clone()andcloneDynamic()methods MapClonewrapper 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.)
Installation #
Add cloner to your pubspec.yaml:
dependencies:
cloner: ^1.0.0
Then run:
dart pub get
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 with Cloner #
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);
MapClone for 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);
Typed vs Dynamic Cloning #
-
Typed cloning (
clone()) preserves generic types but throwsUnsupportedTypedCloneExceptionif a nested plainMapis encountered. Wrap nested maps withMapClonefor typed cloning. -
Dynamic cloning (
cloneDynamic()) returnsdynamicelement/value types and handles any nested structure including plain maps.
License #
This work is licensed under the BSD 3-Clause License.