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.
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.
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.
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
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
}
Using a package like freezed, you could make deep copies of the complex objects.
Although one downside is that the objects are immutable and you cannot make shallow copies of it. But again, it depends on your use case and how you want your objects to be.
Related
I am trying to understand the rational behind using writeByte(3) in the write method in Hive TypeAdapter.
Please consider the class:
#HiveType()
class Person{
#HiveField(0)
String name;
#HiveField(1)
int age;
}
In the TypeAdapter below It is easy to understand the read method, since it is just reads sequentially each field.
However, I'm trying to figure out why the same mechanism does not apply to the write, instead of using ..writeByte(...) just before each field. And, what is the meaning of the first ..writeByte(2)?
class PersonAdapter extends TypeAdapter<Person> {
#override
Person read(BinaryReader reader) {
var numOfFields = reader.readByte();
var fields = <int, dynamic>{
for (var i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return Trips()
..name = fields[0] as String
..age = fields[1] as int;
}
#override
void write(BinaryWriter writer, Person obj) {
writer
..writeByte(2) // Why this here? (sometimes I see writeByte(3) !! )
..writeByte(0)
..write(obj.name)
..writeByte(1)
..write(obj.age);
}
}
Thanks for any clarification.
I know nothing about Hive but if you take a look at the builder which create this write method you can see the following:
String buildWrite() {
var code = StringBuffer();
code.writeln('writer');
code.writeln('..writeByte(${getters.length})');
for (var field in getters) {
var value = _convertIterable(field.type, 'obj.${field.name}');
code.writeln('''
..writeByte(${field.index})
..write($value)''');
}
code.writeln(';');
return code.toString();
}
https://github.com/hivedb/hive/blob/59ad5403593283233d922f62f76832c64fa33a3b/hive_generator/lib/src/class_builder.dart#L122
So based on this we can conclude the first writeByte is the length of getters. The next one is the index of the first getter (0) following by the value and next getter (1) with value and so on.
This makes sense since the protocol properly needs to know how many fields it can expect to get.
After trying several ways to get to know about the original types of the classproperties from a reflected class.
For example, this is an Animator class and Animator has four properties with it (id, name, costPerHour and reviews)
class Animator {
int id;
String name;
double costPerHour;
List<Review> reviews;
}
...And from the Animator class, I wanna loop through the properties and I also wanna know if for example
the original type of id (is id an int, a String or an object?).
With the discovery that declarations doesn't give the needed information in this situation;
var _refClass = reflectClass(Animator);
var classProperties = _refClass.declarations.values.forEach((property) => {
// property.runtimeType returns only _DeclarationMirror of _propertyname_
}); // What is the original type each property??
Help is really appreceated, thanks.
I have model
class Model {
int counter;
}
now I get a Model object at runtime, so I mark it as dynamic.
dynamic model = new Model();
int counter = 3;
What would be possible way to do equivalent of
model['counter'] = counter;
one thing to mention is that counter from Model is used in the app somewhere, so tree shaking wont remove it.
I am looking for any solution.
class Model {
int counter;
var counterPath = "any way to reference counter field, so I can give the value to it";
}
open to any suggestion or hack :)
One way is to implement a mapping between string name and actual field like
class Model {
int counter;
// reading: print(model['counter']);
operator [](String name) {
switch(name) {
case 'counter':
return counter;
default: throw 'Field $name doesn't exist' in $runtimeType';
}
// writing: model['counter'] = counter;
operator []=(String name, dynamic value) {
switch(name) {
case 'counter':
counter = value;
break;
default: throw 'Field $name doesn't exist' in $runtimeType';
}
}
I'd not recommend this, as using model['string'] makes you loose a lot in maintainability.
But if you truly need it, the best way to achieve this is by using code generation.
In your case, json_serializable may be a good idea. As it will generate a toJson method and a MyClass.fromJson constructor ; without having to change your model in any way.
In the end you'd have
#JsonSerializable()
class Model extends Object with _$ModelSerializerMixin {
int couter;
Model();
factory Model.fromJson(Map<String, dynamic> json) => _$ModelFromJson(json);
}
Which allows you to run the following :
final model = new Model()
..counter = 42;
final modelJson = model.toJson();
modelJson['counter'] = 43;
final result = new Model.fromJson(modelJson); // counter = 43
This may not be the most optimal in term of performances. But it's far less likely to have a bug or require constant edit.
That was for the easy solution.
But that's ugly ; please don't do that.
If you have a real use case where you need reflection then instead of loosing all type check ; create a custom code generator.
There are a few awesome toolings for code generators in dart.
Starting with the lower layer source_gen followed by build to name a few.
Dart team created a lot of tools for code generation ; go take a look !
Some said that private properties can be used to change class definition without changing existing codes depending on the class before.
For example;
main() {
var p1 = new Project();
p1.name = 'Breeding';
p1.description = 'Managing the breeding of animals';
print('$p1');
// prints: Project name: Breeding - Managing the breeding of animals
}
class Project {
String name, description;
toString() => 'Project name: $name - $description';
}
Now, class Project is changed as below using private variables.
main() {
var p1 = new Project();
p1.name = 'Breeding';
p1.description = 'Managing the breeding of animals';
print('$p1');
// prints: Project name: BREEDING - Managing the breeding of animals
var p2 = new Project();
p2.name = 'Project Breeding of all kinds of animals';
print("We don't get here anymore because of the exception!");
}
class Project {
String _name; // private variable
String description;
String get name => _name == null ? "" : _name.toUpperCase();
set name(String prName) {
if (prName.length > 20)
throw 'Only 20 characters or less in project name';
_name = prName;
}
toString() => 'Project name: $name - $description';
}
What does it mean that;
(due to the private properties introduced) The code that already existed in main (or in general, the client code that uses this property) does not need to change
The author of the above codes(Learning Dart) said that, due to the newly inserted private property(_name), the existing codes like 'main()' are NOT affected by the change of properties in the Project class.
That's what I can't understand. How newly inserted private properties in existing classes can be a way to keep other codes depending on those classes untouched or safe?
I still don't know what your actual question is.
You can use private fields, classes and functions/methods to reorganize (refactor) your code and
as long as the public API is not affected and the users of the library do not depend on the internal behavior of the classes the users should not recognize the change.
Privacy is mostly a communication medium to indicate that some members (public) are intended to be accessed by API users and private members are an implementation detail the API user should not care about.
Don't confuse privacy with security. Reflection usually allows access to private members.
If this is not the answer you were looking for please add a comment or improve your question.
I have a class
class Account extends Stuff{
String name;
newObject(){
return new Account();
}
}
inside the Stuff class I have a method
//generates list of objects of the same type
//as given object and fills attribute
generateObjectsFromExisting(names)
{
List list = new List();
InstanceMirror instanceMirror = reflect(this);
Symbol formatSymbol = new Symbol("newObject");
for(var name in names){
//calles newObject function from this and returns a new object
var newInstanceObject = instanceMirror.invoke(formatSymbol, []);
Symbol symbol = new Symbol("name");
InstanceMirror field = newInstanceObject.setField(symbol,name);
list.add(newInstanceObject.reflectee)
}
return list;
}
so when writing
main(){
var account = new Account();
List accounts = new List();
accounts = account.generateObjectsFromExisting(['tim','tom']);
print(account.name) // returns null
print(accounts[0].name) // returns tim
print(accounts[1].name) // returns tom
}
the problems with this way are
1 'generateObjectsFromExisting()' is on the 'account' object and not on Account
2 I have to manually add the "newObject" Method to every single class I implement.
I would prefer a static Method like 'Account.generateObjectsFromExisting()'
but how to to access 'this' (since its not available in static)
so I can say "this.new()" or something equivalent to "new Account();" eg "new this();"
and therefor be able to only have one 'newObject' function inside Stuff or maybe wont need it at all.
so now my code would look like this
class Account extends Stuff{
String name;
}
in Stuff
static generateObjectsFromExisting(names)
{
List list = new List();
for(var name in names){
var object = new this();
object.name = name;
list.add(object)
}
return list;
}
in main
main(){
// returns list of Accounts filled with names
accounts = Account.generateObjectsFromExisting(['tim','tom']);
print(accounts[0].name) // returns tim
print(accounts[1].name) // returns tom
}
if you can show me a way to access the Class to do something like this.new(); or new this(); then obviously the class 'Account' needs to be accessed and not the extended 'Stuff'
if the 'this' approach is not possible, then maybe you can show me a way how to access the Class from within an already existing object
like
generateObjectsFromExisting(names)
{
List list = new List();
var class = this.class;
var newObject = class.new():
...
}
or is my current approach the only solution. .. hope not :)
thank you
There are two ways I can think of at the moment. But both of them are pretty close to your initial solution as they both use reflection..
The non-static solution:
class Stuff {
generateObjectsFromExisting(List<String> names) {
var cm = reflectClass(this.runtimeType);
return names.map((name) {
var newInstance = cm.newInstance(const Symbol(''), []).reflectee;
newInstance.name = name;
return newInstance;
}).toList();
}
}
The static solution:
class Stuff {
static generateObjectsFromExisting(type, List<String> names) {
var cm = reflectClass(type);
return names.map((name) {
var newInstance = cm.newInstance(const Symbol(''), []).reflectee;
newInstance.name = name;
return newInstance;
}).toList();
}
}
You would call the static solution like this:
var accounts = Stuff.generateObjectsFromExisting(Account, ['tim', 'tom']);
There might be another solution involving factory constructors but can't think of any right now. Also, this code would easily break when you get another subclass of Stuff that does not have a name attribute. I don't know if you really intended on putting that attribute on Account instead of Stuff.
Also answering you 'Class'-Question. There is no class in Dart, there is only the Type and to get it you can do:
Type type1 = Account;
Type type2 = account.runtimeType;
But the Type doesn't have any methods you could use to create a new instance.