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?
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);
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.
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);
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.
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,
);
}
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: 'joaosilva#gmail.com',
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
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);
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;
}
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());
Trying using a Copyable interface provided by Dart.
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
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');
}
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.
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);
}
}
An example of Deep copy in dart.
void main() {
Person person1 = Person(
id: 1001,
firstName: 'John',
lastName: 'Doe',
email: 'john.doe#email.com',
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 : john.doe#email.com
alive : true
=hashCode=: 515186678
Object: person2
id : 1001
fName : John
lName : Doe
email : john.doe#email.com
alive : true
=hashCode=: 686393765
// 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;
}
This works for me.
Use the fromJson and toJson from your Object's Class on JSON serializing
var copy = ObjectClass.fromJson(OrigObject.toJson());
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));
}
}
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));
}
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]);
I am trying to generate mock data using relay for storybook.
My query is
const QUERY_LIST = graphql`
query modelControllerAllUsersQuery #relay_test_operation {
allUsers {
pageInfo {
hasNextPage
}
edges {
node {
id
firstName
lastName
}
}
}
}
`
and provided RelayEnvironmentProvider as a decorator to the story. I'm trying to return some default values to my query using custom mock resolvers.
const customMockResolvers = {
...mockResolvers,
allUsers:() => ({
pageInfo:{
hasNextPage:false,
},
edges:[
{
node:{
id :'id',
firstName:'fname',
lastName :'lname',
},
},
],
}),
};
and calling it as
(operation) => MockPayloadGenerator.generate(operation, customMockResolvers)
I don't seem to be able to get the default values returned.
Currently, it is returning
{"allUsers":{"pageInfo":{"hasNextPage":false},"edges":[{"node":{"id":"<UserNode-mock-id-1>","firstName":"<mock-value-for-field-\"firstName\">","lastName":"<mock-value-for-field-\"lastName\">"}}]}}
What am I doing wrong?
When using the #relay-test-operation, the keys within your customMockResolvers object must match the type name of the fields, which can be different from the field names themselves.
For example, you could have the following in your schema:
type Foo {
id: ID!
name: String!
}
and the following query:
query FooQuery #relay_test_operation {
foo {
id
name
}
}
Then the customMockResolvers object would look like this:
const customMockResolvers = {
Foo: () => ({
id: "fooId",
name: "fooName"
})
}
Notice that I'm passing in Foo as the key instead of foo.
You can check your schema and see what the the type name of allUsers is. I suspect it would be something like AllUsers or allUsersConnection, or something similar.
Also, if you're interested in creating Storybook stories for Relay components, I created a NPM package just for that: https://www.npmjs.com/package/use-relay-mock-environment
It doesn't require adding the #relay-test-operation directive to your query, and instead relies only on resolving the String type (which is the default for all scalar properties). You can of course still add the #relay-test-operation directive and also extend the resolvers by providing customResolvers in the config.
You can also extend the the String resolver as well, by providing extendStringResolver in the config.
Feel free to review the source code here if you want to implement something similar: https://github.com/richardguerre/use-relay-mock-environment.
Note: it's still in its early days, so some things might change, but would love some feedback!
I've got two Constructs being created in one Stack, and during synthesis I would like to merge a bunch of properties and use the merged result.
The reason for this is to split creation of resources into a specific look in the Stack. In other words, I want to initialize two Constructs in the one Stack, but when CDK deploys the stack, I would like properties from both to be merged, with the resource(s) created in one of the Stacks, (Environment in my example below) being able to use the merged properties from both Constructs.
Here is where I've got to so far, but when running a cdk synth the resulting someProperties object does not reflect the items I've sent through from my ExampleThing Construct that was initialized in the same Stack.
export class IExampleEnvironment extends Construct {
readonly someId?: string;
}
export interface ExampleProps {
readonly environment: Environment;
readonly a: string;
readonly b: string;
}
export class Environment extends IExampleEnvironment {
private someProperties: any;
constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id);
new ExampleResourceThatUsesSomeProperties(this, 'Example' {
foo: this.someProperties // this is where I would like to use some properties that are brought in from another Construct (in the same stack as Environment)
});
}
public addSomeProperties(someProps: ExampleProps) {
this.someProperties = { A: someProps.a, B: someProps.b };
}
}
export class ExampleThing extends Construct {
constructor(scope: Construct, id: string, props: ExampleProps) {
super(scope, id);
props.environment.addSomeProperties(props);
}
}
// Creating the Stack...
const environment = new Environment(this, 'Environment', { someId: "123" });
const exampleThing = new ExampleThing(this, 'ExampleThing', props);
Currently, I'm using a reference to the Environment property sent in, and then calling the internal addSomeProperties() method on it, hoping that would work. I suspect this is my issue, but I'm not sure of how to achieve this.
The important thing here is that the Environment stack must be created first, as the ExampleThing stack needs to supply a environment property as part of its ExampleProps and pass the Environment in for that.
Is there a better way that would work?
So I'll admit this is a very niche requirement, and not really the way CDK is meant to be used, but I wanted to get this very specific use case working.
I dug in a little more and found that it is possible by overriding the prepare() method in the Construct. Here is my working example, which shows how one construct can create an S3 bucket, and another can update this instance's S3 bucket purely by creating an instance and passing in the other instance:
thing.ts:
import { Construct } from "#aws-cdk/core";
import { Foo } from "./foo";
export interface ExampleThingProps {
readonly foo: Foo;
readonly a: string;
readonly b?: string;
}
export class ExampleThing extends Construct {
constructor(scope: Construct, id: string, props: ExampleThingProps) {
super(scope, id);
props.foo.addSomeProperties(props);
}
}
foo.ts:
import { Construct, StackProps } from "#aws-cdk/core";
import * as s3 from "#aws-cdk/aws-s3";
import { ExampleThingProps } from "./thing";
import { IBucket } from "#aws-cdk/aws-s3";
export class IExampleFoo extends Construct {
readonly someId?: string;
}
export interface FooProps {
bucketName: string;
}
export class Foo extends IExampleFoo {
private someProperties: any;
private theBucket: IBucket;
constructor(scope: Construct, id: string, props: FooProps) {
super(scope, id);
this.someProperties = { A: props.bucketName };
this.theBucket = new s3.Bucket(this, 'ExampleBucket', {
bucketName: this.someProperties.A
});
}
prepare() {
const firstBucket = this.node.tryFindChild("ExampleBucket");
if (firstBucket) {
console.log('found first bucket node: ' + firstBucket.node.uniqueId);
const removed = this.node.tryRemoveChild(firstBucket.node.id);
console.log('removed the found node: ' + removed);
}
this.theBucket = new s3.Bucket(this, 'ExampleBucket', {
bucketName: this.someProperties.A
});
}
public addSomeProperties(someProps: ExampleThingProps) {
console.log('updates the existing properties...');
this.someProperties = { A: someProps.a, B: someProps.b };
}
}
Usage:
const foo = new Foo(this, 'Foo', {
bucketName: "this-is-the-name-set-from-foo-construction"
});
const thing = new ExampleThing(this, 'Thing', {
foo: foo,
a: "shiny-new-name"
});
If you don't initialize a new ExampleThing then the bucket will be named this-is-the-name-set-from-foo-construction.
If you initialize a new ExampleThing, then the bucket's name will be changed for synthesize (and will apply in a cdk diff or deploy and be named shiny-new-name.
I think you are looking to create dependencies between the resources, here is an example
const app = new App();
const myFirstStack = new MyFirstStack(
app,
stackNameone
);
const mySecondStack = new MySecondStack(
app,
stackNametwo
);
mySecondStack.addDependency(myFirstStack, "Depends on resources");
You can do this with most resources in CDK so whichever resources you want created first add it as a dependency like the above example.
According to this bug and this bug, ssm.StringListParameter doesn't work in the CDK due to some issues with CFT.
I need to be able to export an arbitrarily length list of subnetIds from one stack and import the list into another using SSM, as the lifetime of subnetIds is completely different than the lifetime of the consumer of subnetIds in a second stack (especially in production, though not in a sandbox). I cannot hard code the subnet IDs, as when creating a sandbox the IDs would vary from sandbox to sandbox. I want the latest version of whatever is in the SSM key.
However, the bugs appear to not be resolvable, and I cannot find a work around.
I tried serializing using JSON, but the item passed around the code is is a late binding Token, which is treated as a string, and the deserialized items is a string [], so it's not possible to get such code to compile.
Here's an example of what I attempted. It doesn't compile:
export function getOtherStackOutputList(stack: cdk.Stack, mangledOtherStackName: string, key: string): string [] {
const globallyUniqueKey = `/${mangledOtherStackName}/${key}`;
const jsonResult = ssm.StringParameter.fromStringParameterName(stack, key + 'SSM', globallyUniqueKey).stringValue;
if (cdk.Token.isUnresolved(jsonResult)) {
return jsonResult;
} else {
const result = JSON.parse(jsonResult);
return result;
}
};
which would be used by code that looks like this:
const efsVpcIsolatedSubnetsIds = StackValueShare.getOtherStackOutputList(this, mangledStackName + efsDbStackSuffix,
'efsTimeSeriesDatabaseVpcIsolatedSubnets');
This works for me:
import { Construct } from '#aws-cdk/core';
import { AwsCustomResource, AwsSdkCall } from '#aws-cdk/custom-resources';
import iam = require("#aws-cdk/aws-iam");
interface SSMParameterReaderProps {
parameterName: string;
region: string;
}
export class SSMParameterReader extends AwsCustomResource {
constructor(scope: Construct, name: string, props: SSMParameterReaderProps) {
const { parameterName, region } = props;
const ssmAwsSdkCall: AwsSdkCall = {
service: 'SSM',
action: 'getParameter',
parameters: {
Name: parameterName
},
region,
physicalResourceId: {id:Date.now().toString()} // Update physical id to always fetch the latest version
};
super(scope, name, { onUpdate: ssmAwsSdkCall,policy:{
statements:[new iam.PolicyStatement({
resources : ['*'],
actions : ['ssm:GetParameter'],
effect:iam.Effect.ALLOW,
}
)]
}});
}
public getParameterValue(): string {
return this.getResponseField('Parameter.Value').toString();
}
}
I have a doubt about using of "promises" in typescript. I'm writting e2e testing framework with protractor and typescript and I would like to make some query to the database in order to use the data retrieved to fill forms or make validations.
I've create a new class "UserService" and the idea is to use some static methods to return data. I've installed the typeOrm library to handle it.
The problem is I cannot find the way to convert the "promise" returns to "strings". How can I do it?
Take a look to the code:
import "reflect-metadata";
import { User } from "././entities/user";
import { ConnectionOptions, Connection, Driver, createConnection } from "typeorm";
const connectionOptions: ConnectionOptions = {
driver: {
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "admin123",
database: "user"
},
entities: [User],
autoSchemaSync: false
};
export class UserService {
static getUserName(userId:number): string {
let us = createConnection(connectionOptions).then(connection => {
return connection.getRepository(User).findOne({Id: userId})
}).then(user => user.name);
return us; //it return an Promise<string>.
}
}
Into the "step" classes, the above class will be used for example as:
let name: string = UserService.getUserName(1);
txtUsername.Sendkeys(name);
Use await/async:
let name = await UserService.getUserName(1);
txtUsername.Sendkeys(name);
This has to be in a function defined as async, and you'll probably want to surround it with a try/catch, and it will not be synchronous, but that's the easiest way to access it.
And have no doubts about promises... they are super awesome.