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)
- Tries to preserve 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().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.builder = MyCustomCloner();
Collection Extensions
Use clone() for typed cloning or cloneDynamic() for untyped cloning. Both respect the current Cloner.builder 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>
final cloneTyped = numbers.clone();
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
Worth Noting 🔍
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.
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).
Refer to public API docs.
Problem-Solution 🤔 (from the maintainers)
static Cloner does not work with isolates (multithreading)
Use cloner implementations directly (imported from package:cloner/base.dart):
BaseCloner cloner = (BaseClonerBuilder()..doTypedClone = true).build();
cloner.cloneMap(map);
cloner.cloneValue(value);
cloner.cloneList(list);
When it comes to collection extensions, if they are going to be used in a separate isolate, don't forget to swap Cloner.builder (Dart isolates have separate memory, so there is no retention of static global state).
Low performance on collection cloning
Follow these practices to squeeze all the juice out of Cloner:
Loops
Each call to instance() instantiates a new object using the builder, so the fewer times it is called, the better – avoid calling it inside loops (this applies to collection extensions as well).
Right tool for the right task
- There is no need to complicate things. No possibility of circular references or enormous collection sizes means no need of counting overhead, just use
BaseCloner - Prefer typed over dynamic cloning (except for Map: it is actually beneficial to dynamically clone maps). Simple lists and sets are cloned faster using typed modes.
Important
Be aware of how certain cloning methods handle concrete type preservation – see Concrete Type Preservation for more info.
Benchmarks 📊
CountedCloner and HashedCloner both extend BaseCloner. While they may introduce some overhead compared to the default, they provide flexibility for different performance scenarios.
Recommended choices based on benchmarks
- JIT Compilation:
- Typed collections:
CountedCloner - Dynamic collections:
HashedCloner
- Typed collections:
- AOT Compilation:
- Lists/Sets:
HashedCloner - Maps
CountedCloner
- Lists/Sets:
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

|- More Charts! -|
JIT/AOT

Cloner Matrix

License
This work is licensed under the BSD 3-Clause License.
Libraries
- base
- Base cloning implementation and interface for custom cloning strategies.
- cloner
- Utilities for producing deep clones
- core
- Core cloning API and contracts for the Cloner library.
- extensions
- Collection cloning extensions for
List,Set, andMap.