16
votes

I would like to clone a complex object (copy values), not referencing, using Dart 2.

Example:

class Person {
  String name;
  String surname;
  City city;
}

class City {
  String name;
  String state;
}

main List<String> args {
  City c1 = new City()..name = 'Blum'..state = 'SC';
  Person p1 = new Person()..name = 'John'..surname = 'Xuebl'..city = c1;


  Person p2 = // HERE, to clone/copy values... Something similar to p1.clone();
}

What would be the way (best practice) to do this?

Update note: This How can I clone an Object (deep copy) in Dart? was posted some time ago. The focus here is to understand if Dart 2 that is bringing many improvements, has a facility for copying complex objects.

4
Because the thread was posted some time ago, it was dealing with the Dart 1 version. My intention was to post a new post to specifically address Dart 2, which is bringing excellent improvements, and could have something to do with cloning complex objects.Muka

4 Answers

9
votes

With the classes you have shown us here, there is nothing shorter than

Person p2 = Person()
  ..name = p1.name
  ..surname = p1.surname
  ..city = (City()..name = p1.city.name..state = p1.city.state);

If you add a clone method to Person and City, then you can obviously use that. There is nothing built in to the language to allow you to copy the state of an object.

I would recommend changing the classes, at least by adding a constructor:

class Person {
  String name;
  String surname;
  City city;
  Person(this.name, this.surname, this.city);
}
class City {
  String name;
  String state;
  City(this.name, this.state);
}

Then you can clone by just writing:

Person P2 = Person(p1.name, p1.surname, City(p1.city.name, p1.city.state));

(And ob-link about names)

I say that there is no language feature to copy objects, but there actually is, if you have access to the dart:isolate library: Sending the object over a isolate communication port. I cannot recommend using that feature, but it's here for completeness:

import "dart:isolate";
Future<T> clone<T>(T object) {
  var c = Completer<T>();
  var port = RawReceivePort();
  port.handler = (Object o) {
    port.close();
    c.complete(o);
  }
  return c.future;
}

Again, I cannot recommend using this approach. It would work for simple objects like this, but it doesn't work for all objects (not all objects can be sent over a communication port, e.g., first-class functions or any object containing a first class function).

Write your classes to support the operations you need on them, that includes copying.

5
votes

My simpler solution just let clone() return a new Person with the current values:

class Person {
  String name;
  String surname;
  City city;
  Person(this.name, this.surname, this.city);
  clone() => Person(name, surname, city);
}

You might further need to recursively clone the objects in your Person. as an example by creating a similar clone() function in the City and using it here as city.clone().
For the strings you will need to check their behavior or also create / add a way for cleaning them.

2
votes

As said, there is no built in solution for that, but if the ideia is to accomplish immutable value types you can check built_value.

https://medium.com/dartlang/darts-built-value-for-immutable-object-models-83e2497922d4

1
votes

I noted that using Map.from() do a shallow copy and not a deep copy.

To do a deep copy of a class containing a Map of anoter Class, one solution can be to use a nammed constructor

class MyClassB {
   int myVar;
   
   // Constructor
   MyClassB(this.id);
   
   // Named Constructor to do a deep clone
   MyClassB.clone(MyClassB b){
     id = b.id;
   }
}

class MyClassA { 
  Map<int,MyClassB> mapOfClassB;

  // Constructor
  MyClassA(this.myClassB)

  // Named constructor to do a deep clone
  MyClassA.clone(MyClassA a){
    Map<int,myClassB> m = {};
    myClassB  = a.mapOfClassB.forEach((k,v)=> m[k] = MyClassB.clone(v)); // Use the clone constructor here, if not the maps in MyClassA and MyClassB will be linked
  }
}

main() {
  var b1 = MyClassB(20);
  var a1 = MyClassA({0:b1});
  
  var a2 = MyClass1A.clone(a1);
  
  a2.mapOfClassB[0].id = 50;
  
  print(a1.mapOfClassB[0].id); // Should display 20
  print(a2.(a1.mapOfClassB[0].id) // Should display 50 
}