george koller
george koller

Reputation: 3941

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

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?

Upvotes: 154

Views: 154616

Answers (23)

Andy Li
Andy Li

Reputation: 41

My Solution:

import 'dart:convert';
Map newMap = json.decode(json.encode(oldMap));

Upvotes: 0

Touseef Ahmed Qureshi
Touseef Ahmed Qureshi

Reputation: 111

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

final clonedData = await compute((dynamic data) => return data));

Upvotes: 1

Irfan Ul Haq
Irfan Ul Haq

Reputation: 1155

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;
  }
 }

Upvotes: 0

JAR.JAR.beans
JAR.JAR.beans

Reputation: 10014

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.

Upvotes: 54

Xavier
Xavier

Reputation: 107

This works for me.

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

var copy = ObjectClass.fromJson(OrigObject.toJson());

Upvotes: 4

C xkey
C xkey

Reputation: 61

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);
  }
}

Upvotes: 6

Regular Jo
Regular Jo

Reputation: 5510

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]);

Upvotes: 0

Baker
Baker

Reputation: 28020

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.

Upvotes: 5

Phill Wiggins
Phill Wiggins

Reputation: 2927

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);

Upvotes: 64

adel parsa
adel parsa

Reputation: 446

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

Upvotes: 4

luckyhandler
luckyhandler

Reputation: 11329

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,
      );
}

Upvotes: 16

Shahrear Bin Amin
Shahrear Bin Amin

Reputation: 1115

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));
}

Upvotes: 0

freedomHongKong
freedomHongKong

Reputation: 101

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);

Upvotes: 7

Loi Pham
Loi Pham

Reputation: 1

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));
    }
 }

Upvotes: 0

Magnus
Magnus

Reputation: 18758

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');
}




Upvotes: 3

Hein Htet Zaw
Hein Htet Zaw

Reputation: 109

// 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;
}

Upvotes: 1

Robin
Robin

Reputation: 554

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

Upvotes: 1

Aayush Goyal
Aayush Goyal

Reputation: 73

Trying using a Copyable interface provided by Dart.

Upvotes: 4

Baig
Baig

Reputation: 4995

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());

Upvotes: 7

maPer77
maPer77

Reputation: 353

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

Upvotes: 12

Gauranga
Gauranga

Reputation: 989

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;
}

Upvotes: 6

computmaxer
computmaxer

Reputation: 1713

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);

Upvotes: 62

Lukas B.
Lukas B.

Reputation: 453

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.

Upvotes: 25

Related Questions