How can I clone an Object (deep copy) in Dart?
Asked Answered
T

23

151

Is there a Language supported way to make a full (deep) copy of an Object in Dart?

If multiple options exist, what are their differences?

Transitive answered 28/10, 2012 at 10:3 Comment(0)
S
54

No as far as open issues seems to suggest:

https://github.com/dart-lang/sdk/issues/3367

And specifically:

... Objects have identity, and you can only pass around references to them. There is no implicit copying.

Systematism answered 28/10, 2012 at 10:19 Comment(14)
Thanks, and I did just end writing a clone method myself for the few classes where I need to make a copy (not just a reference). I was spoiled by Ruby - thought this was a standard language feature.Transitive
it's suppose to be a standard language feature =_="Incoordination
even in official dart polymer tutorial they implement copyCodelab() function by hand. There really suppose to be such stdlib functionality. I'll file a bug for that.Condensable
how about 6 years later? is this answer still a truth?Gunlock
Well this is disappointing. To be fair, many many languages suffer from this issue, even Go has no way to copy an object like you can in C++ or Rust. Anyway here is a recent issue I found for this.Osteotomy
This is the wrong answer. Just because a language has pass-by-reference calls, doesn't mean deep copying is not supported.Uncircumcised
@Uncircumcised Would be great if you can elaborate. The need is a Map of Maps. Say, a complex configuration, The requirement is to be fully clone this, such that it's protected (and maybe even based on need, changed) inside a specific process, but the original object remains unchanged. many languages supports pass-by-ref but also have deep copying out of the box.Systematism
Sure language like scala support deep copies by provided methods to reflect the constructor parameter names, types and values (and you can use them to serialize many classes). Language like python provide reflection methods to find an objects class names and attributes (and use them to perform auto-serialization and deep copying). In 2020, these features from 1990 are what we’ve come to expect in a programmable language.Uncircumcised
Sorry. Lost you at 1990. deepcopy is a convenient method, syntactic sugar. Any one can implement this (some ways for that, from full serialization and deserialization to recursive looping over the object). The question here is, does Dart has this out of the box, The answer is - No. How can this be a wrong answer? Is there a discussion about "is deep copy a good idea", sure. But I don't think it's related.Systematism
Super lame, give-up-the-ship answer. If nothing else you can loop through each of the objects properties to create a new object.Newburg
This behaviour comes automatically with type promotion.Friary
@Newburg No, you can't. The compiler cannot automatically choose which constructor should be invoked for each member, if a constructor is even available. What would the compiler do if an object has a member with declared with the type of an abstract base class? Or, if you expect that "looping over each of the object's properties" be done at runtime, then that would require runtime reflection, which would penalize performance and code size.Poteat
how about 10 years later? is this answer still a truth?Contempt
@Contempt Dart does not have copy constructors, so nothing has changed.Poteat
H
60

Darts built-in collections use a named constructor called "from" to accomplish this. See this post: Clone a List, Map or Set in Dart

Map mapA = {
    'foo': 'bar'
};
Map mapB = new Map.from(mapA);
Heirship answered 17/2, 2015 at 3:26 Comment(9)
This might work for a simple <String,String> Map, but it will not for more complex maps like <String,Map>. I had the same idea like you, but it didnt work.Nightspot
from is a constructor. use new Map.from(mapA);Melodramatic
@MosheShaham i don't get itGunlock
@jerinho you are correct that Moshe Shaham's solution does not work for nested maps.Erythrite
This is not an answer to the question. As asked, the answer to the question is "There is no general way to create a copy of an arbitrary object in Dart". There can be ways to create copies for specific types of objects, but enumerating those would be an overly broad question.Poteat
Additionally, this is an outdated way to copy Maps. Since at least Dart 2, using Map.from/Set.from/List.from is discouraged since they lose type information. Using Map.of/Set.of/List.of is better, or for Sets and Lists, .toSet()/.toList()`.Poteat
Also, I somehow forgot to mention that this does not create a deep copy of a Map. It creates a shallow copy.Poteat
ideally we don't want to pay the price of (de)serialization just to copy an object.Addam
This does not answer the question. It is asking about objects not collections of objects.Reborn
B
59

Late to the party, but I recently faced this problem and had to do something along the lines of :-

class RandomObject {

  RandomObject(this.x, this.y);

  RandomObject.clone(RandomObject randomObject): this(randomObject.x, randomObject.y);

  int x;
  int y;
}

Then, you can just call copy with the original, like so:

final RandomObject original = RandomObject(1, 2);
final RandomObject copy = RandomObject.clone(original);
Blondy answered 10/1, 2019 at 20:48 Comment(4)
Hi @Phill I copy/paste your code and it is not working. Can you improve it?Aristotelianism
Hey @pedromassango. It's difficult to improve that code. Send me your code or implementation and I can show you. Something like:- ```var newObject = Random object.clone(oldObject) will return a copied instance.Blondy
It was giving me a error, I just replaced super by thisAristotelianism
Not sure why this was downvoted either. It's the closest and cleanest possible way to clone an object without using reflection.Blondy
S
54

No as far as open issues seems to suggest:

https://github.com/dart-lang/sdk/issues/3367

And specifically:

... Objects have identity, and you can only pass around references to them. There is no implicit copying.

Systematism answered 28/10, 2012 at 10:19 Comment(14)
Thanks, and I did just end writing a clone method myself for the few classes where I need to make a copy (not just a reference). I was spoiled by Ruby - thought this was a standard language feature.Transitive
it's suppose to be a standard language feature =_="Incoordination
even in official dart polymer tutorial they implement copyCodelab() function by hand. There really suppose to be such stdlib functionality. I'll file a bug for that.Condensable
how about 6 years later? is this answer still a truth?Gunlock
Well this is disappointing. To be fair, many many languages suffer from this issue, even Go has no way to copy an object like you can in C++ or Rust. Anyway here is a recent issue I found for this.Osteotomy
This is the wrong answer. Just because a language has pass-by-reference calls, doesn't mean deep copying is not supported.Uncircumcised
@Uncircumcised Would be great if you can elaborate. The need is a Map of Maps. Say, a complex configuration, The requirement is to be fully clone this, such that it's protected (and maybe even based on need, changed) inside a specific process, but the original object remains unchanged. many languages supports pass-by-ref but also have deep copying out of the box.Systematism
Sure language like scala support deep copies by provided methods to reflect the constructor parameter names, types and values (and you can use them to serialize many classes). Language like python provide reflection methods to find an objects class names and attributes (and use them to perform auto-serialization and deep copying). In 2020, these features from 1990 are what we’ve come to expect in a programmable language.Uncircumcised
Sorry. Lost you at 1990. deepcopy is a convenient method, syntactic sugar. Any one can implement this (some ways for that, from full serialization and deserialization to recursive looping over the object). The question here is, does Dart has this out of the box, The answer is - No. How can this be a wrong answer? Is there a discussion about "is deep copy a good idea", sure. But I don't think it's related.Systematism
Super lame, give-up-the-ship answer. If nothing else you can loop through each of the objects properties to create a new object.Newburg
This behaviour comes automatically with type promotion.Friary
@Newburg No, you can't. The compiler cannot automatically choose which constructor should be invoked for each member, if a constructor is even available. What would the compiler do if an object has a member with declared with the type of an abstract base class? Or, if you expect that "looping over each of the object's properties" be done at runtime, then that would require runtime reflection, which would penalize performance and code size.Poteat
how about 10 years later? is this answer still a truth?Contempt
@Contempt Dart does not have copy constructors, so nothing has changed.Poteat
M
26

I guess for not-too-complex objects, you could use the convert library:

import 'dart:convert';

and then use the JSON encode/decode functionality

Map clonedObject = JSON.decode(JSON.encode(object));

If you're using a custom class as a value in the object to clone, the class either needs to implement a toJson() method or you have to provide a toEncodable function for the JSON.encode method and a reviver method for the decode call.

Milone answered 28/10, 2014 at 18:41 Comment(4)
it loses type information, and it only works for object types that can be represented by JSON. Most importantly it's way slower than it should be.Osteotomy
It works for me when I customise it into ClassName.fromJson(jsonDecode(jsonEncode(object)));. Thanks.Diazotize
Great solution, since we have already added JSON support for server communicationIsabea
Yes, that works great when you already have fromJson() in your modelsRosinarosinante
S
15

Unfortunately no language support. What I did is to create an abstract class called Copyable which I can implement in the classes I want to be able to copy:

abstract class Copyable<T> {
  T copy();
  T copyWith();
}

I can then use this as follows, e.g. for a Location object:

class Location implements Copyable<Location> {
  Location({
    required this.longitude,
    required this.latitude,
    required this.timestamp,
  });

  final double longitude;
  final double latitude;
  final DateTime timestamp;

  @override
  Location copy() => Location(
        longitude: longitude,
        latitude: latitude,
        timestamp: timestamp,
      );

  @override
  Location copyWith({
    double? longitude,
    double? latitude,
    DateTime? timestamp,
  }) =>
      Location(
        longitude: longitude ?? this.longitude,
        latitude: latitude ?? this.latitude,
        timestamp: timestamp ?? this.timestamp,
      );
}
Support answered 27/3, 2020 at 19:32 Comment(0)
V
11

To copy an object without reference, the solution I found was similar to the one posted here, however if the object contains MAP or LIST you have to do it this way:

class Item {
  int id;
  String nome;
  String email;
  bool logado;
  Map mapa;
  List lista;
  Item({this.id, this.nome, this.email, this.logado, this.mapa, this.lista});

  Item copyWith({ int id, String nome, String email, bool logado, Map mapa, List lista }) {
    return Item(
      id: id ?? this.id,
      nome: nome ?? this.nome,
      email: email ?? this.email,
      logado: logado ?? this.logado,
      mapa: mapa ?? Map.from(this.mapa ?? {}),
      lista: lista ?? List.from(this.lista ?? []),
    );
  }
}
Item item1 = Item(
    id: 1,
    nome: 'João Silva',
    email: '[email protected]',
    logado: true,
    mapa: {
      'chave1': 'valor1',
      'chave2': 'valor2',
    },
    lista: ['1', '2'],
  );

// -----------------
// copy and change data
Item item2 = item1.copyWith(
    id: 2,
    nome: 'Pedro de Nobrega',
    lista: ['4', '5', '6', '7', '8']
  );

// -----------------
// copy and not change data
Item item3 = item1.copyWith();

// -----------------
// copy and change a specific key of Map or List
Item item4 = item1.copyWith();
item4.mapa['chave2'] = 'valor2New';

See an example on dartpad

https://dartpad.dev/f114ef18700a41a3aa04a4837c13c70e

Vespertine answered 30/5, 2020 at 2:24 Comment(4)
The problem of this solution is when you want to set a null value to variable.Circularize
Really, a suggestion that I can think of now is to set it to null after copying ...: // --------------------------------------- Item item2 = item1.copyWith( id: 2, nome: 'Pedro de Nobrega', lista: ['4', '5', '6', '7', '8'] ); item2.email = null; // ---------------------------------------Vespertine
Yeah, but this would not work in a immutable objected, where everything is final. I have a suggestion: Item copyWith({ int Function() id, String Function() name .. }) { return Item( id: id?.call() ?? this.id, nome: nome?.call() ?? this.name, ... ); } Now you can call: item.copyWith(name: () => null);Circularize
Excellent. Just using the copyWith() is a good solution. Thanks.Acoustician
V
7

It only works for object types that can be represented by JSON.

ClassName newObj = ClassName.fromMap(obj.toMap());

or

ClassName newObj = ClassName.fromJson(obj.toJson());
Vicechancellor answered 17/1, 2020 at 8:2 Comment(2)
In this case, you'll also need to write fromJson() for all the child classes as well which a lot of work to be done.Volcano
Yes, you can use https://mcmap.net/q/157033/-how-can-i-clone-an-object-deep-copy-in-dart solution too if you don't want to implement fromJson()Vicechancellor
U
7

With reference to @Phill Wiggins's answer, here is an example with .from constructor and named parameters:

class SomeObject{
  String parameter1;
  String parameter2;

  // Normal Constructor
  SomeObject({
    this.parameter1,
    this.parameter2,
  });

  // .from Constructor for copying
  factory SomeObject.from(SomeObject objectA){
    return SomeObject(
      parameter1: objectA.parameter1,
      parameter2: objectA.parameter2,
    );
  }

}

Then, do this where you want to copy:

SomeObject a = SomeObject(parameter1: "param1", parameter2: "param2");
SomeObject copyOfA = SomeObject.from(a);
Unwrap answered 28/5, 2021 at 10:0 Comment(2)
This is the simplest and most straightforward approach. I tried several measures and they are all too complicated and not worth it.Dalpe
This solution works very well. Beware that making a copy from a function within the class will still return the reference. The copy has to be made in another class in order to work.Contagion
M
6

Let's say you a have class

Class DailyInfo

  { 
     String xxx;
  }

Make a new clone of the class object dailyInfo by

 DailyInfo newDailyInfo =  new DailyInfo.fromJson(dailyInfo.toJson());

For this to work your class must have implemented

 factory DailyInfo.fromJson(Map<String, dynamic> json) => _$DailyInfoFromJson(json);


Map<String, dynamic> toJson() => _$DailyInfoToJson(this);

which can be done by making class serializable using

@JsonSerializable(fieldRename: FieldRename.snake, includeIfNull: false)
Class DailyInfo{ 
 String xxx;
}
Mariannamarianne answered 25/7, 2019 at 17:30 Comment(2)
Cloning using JSON is no good idea. It limits the objects you can clone & reduces performance.Chasse
this also doesn't work well if the object has a contains a list of other serializable objects. For example Order has a list of products... even if products are serializable you will see an issue when there's a product in the order.Novice
R
5

There's no API for cloning/deep-copying built into Dart.

We have to write clone() methods ourselves & (for better or worse) the Dart authors want it that way.

Deep copy Object /w List

If the Object we're cloning has a List of Objects as a field, we need to List.generate that field and those Objects need their own clone method.

Example of cloning method (copyWith()) on an Order class with a List field of objects (and those nested objects also have a copyWith()):

  Order copyWith({
    int? id,
    Customer? customer,
    List<OrderItem>? items,
  }) {
    return Order(
      id: id ?? this.id,
      customer: customer ?? this.customer,
      //items: items ?? this.items, // this will NOT work, it references 
      items: items ?? List.generate(this.items.length, (i) => this.items[i].copyWith()),
    );
  }

Gunter mentions this here.

Note, we cannot use List.from(items) nor [...items]. These both only make shallow copies.

Rental answered 17/3, 2022 at 18:10 Comment(2)
Don't we need to use also customer: customer ?? this.customer.copyWith()?Cringle
This should be the correct answer. Addittionally u should extend your class from Equatable...this is how I work with the BLoC pattern and I have no problemsLeaseholder
K
5

Dart does not share Memory within multiple threads (isolate), so...

extension Clone<T> on T {
  
  /// in Flutter
  Future<T> clone() => compute<T, T>((e) => e, this);

  /// in Dart
  Future<T> clone() async {
    final receive = ReceivePort();
    receive.sendPort.send(this);
    return receive.first.then((e) => e as T).whenComplete(receive.close);
  }
}

Kalmick answered 11/4, 2022 at 9:38 Comment(0)
T
4

Trying using a Copyable interface provided by Dart.

Triazine answered 22/1, 2020 at 1:34 Comment(2)
seems good but the owner doesnt seem to maintain the repo. I dont suggest using random 3rd party packages like this.Flemming
I personally prefer the copy_with_extension_gen 1.4.0 package, it basically does the same thing but gives you more control and is being maintainedOutpour
L
4

there is an easier way for this issue just use ... operator for example, clone a Map

Map p = {'name' : 'parsa','age' : 27};
Map n = {...p};

also, you can do this for class properties. in my case, I was needed to clone a listed property of a class. So:

class P1 {
List<String> names = [some data];
}

/// codes
P1 p = P1();
List<String> clonedList = [...p.names]
// now clonedList is an unreferenced type
Loiseloiter answered 10/11, 2021 at 14:16 Comment(3)
This will copy the Map itself, but it will not perform a deep copy of the Maps keys and values. (Map keys should be immutable objects, though.)Poteat
yes, unfortunately, I think this is one of the dart language weaknesses. it hasn't a safe way to simple or deep clone, especially for classesLoiseloiter
This works for me when i combine it with equatable to do comparisonsGeode
B
4

This works for me.

Use the fromJson and toJson from your Object's Class on JSON serializing

var copy = ObjectClass.fromJson(OrigObject.toJson());
Bukavu answered 5/8, 2022 at 4:17 Comment(0)
S
3

There is no built-in way of deep cloning an object - you have to provide the method for it yourself.

I often have a need to encode/decode my classes from JSON, so I usually provide MyClass fromMap(Map) and Map<String, dynamic> toJson() methods. These can be used to create a deep clone by first encoding the object to JSON and then decoding it back.

However, for performance reasons, I usually implement a separate clone method instead. It's a few minutes work, but I find that it is often time well spent.

In the example below, cloneSlow uses the JSON-technique, and cloneFast uses the explicitly implemented clone method. The printouts prove that the clone is really a deep clone, and not just a copy of the reference to a.

import 'dart:convert';

class A{
  String a;
  A(this.a);
  
  factory A.fromMap(Map map){
    return A(
        map['a']
   );
  }
  
  Map<String, dynamic> toJson(){
    return {
      'a': a
    };
  }
  
  
  A cloneSlow(){
    return A.fromMap(jsonDecode(jsonEncode(this)));
  }

  A cloneFast(){
    return A(
      a
    );
  }
  
  
  @override
  String toString() => 'A(a: $a)';
}

void main() {
  A a = A('a');
  A b = a.cloneFast();
  b.a = 'b';
  
  print('a: $a   b: $b');
}




Stylet answered 30/12, 2020 at 9:7 Comment(0)
K
1

An example of Deep copy in dart.

void main() {
  Person person1 = Person(
      id: 1001,
      firstName: 'John',
      lastName: 'Doe',
      email: '[email protected]',
      alive: true);

  Person person2 = Person(
      id: person1.id,
      firstName: person1.firstName,
      lastName: person1.lastName,
      email: person1.email,
      alive: person1.alive);

  print('Object: person1');
  print('id     : ${person1.id}');
  print('fName  : ${person1.firstName}');
  print('lName  : ${person1.lastName}');
  print('email  : ${person1.email}');
  print('alive  : ${person1.alive}');
  print('=hashCode=: ${person1.hashCode}');

  print('Object: person2');
  print('id     : ${person2.id}');
  print('fName  : ${person2.firstName}');
  print('lName  : ${person2.lastName}');
  print('email  : ${person2.email}');
  print('alive  : ${person2.alive}');
  print('=hashCode=: ${person2.hashCode}');
}

class Person {
  int id;
  String firstName;
  String lastName;
  String email;
  bool alive;
  Person({this.id, this.firstName, this.lastName, this.email, this.alive});
}

And the output below.

id     : 1001
fName  : John
lName  : Doe
email  : [email protected]
alive  : true
=hashCode=: 515186678

Object: person2
id     : 1001
fName  : John
lName  : Doe
email  : [email protected]
alive  : true
=hashCode=: 686393765
Korykorzybski answered 17/11, 2020 at 1:45 Comment(0)
F
1

// Hope this work

 void main() {
  List newList = [{"top": 179.399, "left": 384.5, "bottom": 362.6, "right": 1534.5}, {"top": 384.4, "left": 656.5, "bottom": 574.6, "right": 1264.5}];
  List tempList = cloneMyList(newList);
  tempList[0]["top"] = 100;
  newList[1]["left"] = 300;
  print(newList);
  print(tempList);
}

List cloneMyList(List originalList) {
 List clonedList = new List();
  for(Map data in originalList) {
    clonedList.add(Map.from(data));
  }
  return clonedList;
}
Frenchpolish answered 7/12, 2020 at 8:44 Comment(1)
This works thanks. Been struggling all day with a bug due to shallow cloning because I always thought Map.from always deep copies and I had been using that.Unpen
M
1

You can get help from the compute function ** Not Recommended ** though.

final clonedData = await compute((dynamic data) => return data));
Mon answered 7/12, 2023 at 10:20 Comment(0)
P
0

make a helper class:

class DeepCopy {
  static clone(obj) {
    var tempObj = {};
    for (var key in obj.keys) {
      tempObj[key] = obj[key];
    }
    return tempObj;
  }
}

and copy what you want:

 List cloneList = [];
 if (existList.length > 0) {
    for (var element in existList) {
        cloneList.add(DeepCopy.clone(element));
    }
 }
Pendant answered 7/1, 2021 at 2:40 Comment(1)
It does not work. Here is the exception on my custom model. Class 'Scenario' has no instance getter 'keys'. Receiver: Instance of 'Scenario' Tried calling: keysJemima
S
0

Let's say, you want to deep copy an object Person which has an attribute that is a list of other objects Skills. By convention, we use the copyWith method with optional parameters for deep copy, but you can name it anything you want.

You can do something like this

class Skills {
  final String name;

  Skills({required this.name});

  Skills copyWith({
    String? name,
  }) {
    return Skills(
      name: name ?? this.name,
    );
  }
}

class Person {
  final List<Skills> skills;

  const Person({required this.skills});

  Person copyWith({
    List<Skills>? skills,
  }) =>
      Person(skills: skills ?? this.skills.map((e) => e.copyWith()).toList());
}

Keep in mind that using only this.skills will only copy the reference of the list. So original object and the copied object will point to the same list of skills.

  Person copyWith({
    List<Skills>? skills,
  }) =>
      Person(skills: skills ?? this.skills);

If your list is primitive type you can do it like this. Primitive types are automatically copied so you can use this shorter syntax.

class Person {
  final List<int> names;

  const Person({required this.names});

  Person copyWith({
    List<int>? names,
  }) =>
      Person(names: names ?? []...addAll(names));
}
Sartor answered 5/8, 2021 at 18:39 Comment(0)
L
0

The accepted answer doesn't provide an answer, and the highest-rated answer 'doesn't work' for more complex Map types.

It also doesn't make a deep copy, it makes a shallow copy which seems to be how most people land on this page. My solution also makes a shallow copy.

JSON-cloning, which a few people suggest, just seems like gross overhead for a shallow-clone.

I had this basically

List <Map<String, dynamic>> source = [{'sampledata', []}];
List <Map<String, dynamic>> destination = [];

This worked, but of course, it's not a clone, it's just a reference, but it proved in my real code that the data types of source and destination were compatible (identical in my case, and this case).

destination[0] = source[0];

This did not work

destination[0] = Map.from(source[0]);

This is the easy solution

destionation[0] = Map<String, dynamic>.from(source[0]);
Lemming answered 10/4, 2022 at 20:10 Comment(0)
G
0

The Kotlin-style copy method could be implemented with the implementation of the prototype pattern as

class LoginState{
  String userName = "";
  String password = "";

  LoginState copy({String? userName, String? password}){
    var loginState = LoginState();
    loginState.userName = this.userName;
    loginState.password = this.password;
    if(userName != null) {
      loginState.userName = userName;
    }
    if(password != null){
      loginState.password = password;
    }
    return loginState;
  }
 }
Gelman answered 26/4, 2023 at 6:56 Comment(0)
F
0

My Solution:

import 'dart:convert';
Map newMap = json.decode(json.encode(oldMap));
Ferbam answered 27/1 at 15:35 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Scalp

© 2022 - 2024 — McMap. All rights reserved.