5

I would like to use the Spread operator (...) on key-value-data with type safety. In TypeScript I would achieve this with Interfaces, but I could not figure out a way of doing this in dart.

Failed approach 1: Use Classes

class Foo {
  int aNumber;
  String aString;
}

Foo a = Foo();
Foo b = { ...a, 'aString': 'ipsum' }; // This literal must be either a map or a set.

Failed approach 2: Use Maps

Map<String, dynamic> a = { 'aNumber': 2, 'aString': 'lorem' };
Map<String, dynamic> b = { ...a, 'aString': 'ipsum' }; // No error, but also no type safety.

Note: In these approaches, b should not be a list.

TypeScript Example of what I need

interface Foo {
  aNumber: number;
  aString: string;
}
const a: Foo = { aNumber: 2, aString: 'lorem' };
const b: Foo = { ...a, aString: 'ipsum' }; // Has all the props of 'a' and overrides 'aString' to 'ipsum'.
Christoph Bühler
  • 2,795
  • 2
  • 28
  • 43
  • What do you mean with no type safety? In the map approach they both have the same type no? if you try to do something like: `Map a = { 'aNumber': "test", 'aString': 'lorem' }; Map b = { ...a };` this will fail at compile time. – Stefano Saitta Apr 06 '20 at 17:39
  • @StefanoSaitta Thanks for responding. The map approach: A list with any entry of the type 'dynamic'. What I need: An object with the entries 'int aNumber' and 'String aString'. – Christoph Bühler Apr 06 '20 at 18:16

2 Answers2

5

You can achieve it with the first approach, but you have two problems with the snippet:

First check that the spread operator is implemented in iterable, so the line Foo a = Foo(); should be replaced with some iterable, a list for example.

The second problem, the compile error on Foo b = { ...a }; is because Dart uses {} for both sets and maps, so you must specify the type by adding one or two type parameters within <>.

Check this example:

  var a = <Foo>[];
  var b = <String>[];  
  var c = <Foo>{...a}; // works, a is Foo list
  var d = <Foo>{...b}; // compile time error, 'String' can't be assigned to the set type 'Foo'.

Since we use only 1 argument in var d = <Foo>{...b}; dart knows that we want a set.

UPDATE:

For this case you cannot use spread operator. Spread operator was introduced to make easy work with Collections, and collections have the type given by the generics parameters. So if your class doesn't implements Iterable you cant call ... on a given instance. You will get a compile time error:

Spread elements in list or set literals must implement 'Iterable'

On the other hand, as you mention the second approach "doesn't give you" type safety because you are using dynamic. And also ONLY compile because you are declaring Map<String, dynamic> a = { 'aNumber': 2, 'aString': 'lorem' };, wich is a map and implements Iterable (you can use ...a).

For your current problem you need to find a different solution, maybe a custom function on Foo class that returns a Map and take care of types, and then use spread operator on that map.

Hope it helps!

jamesdlin
  • 81,374
  • 13
  • 159
  • 204
fvillalba
  • 963
  • 10
  • 18
  • Thanks for the quick response. If I understand your example correctly, you are creating a list of 'Foo' and using the spread operator with it. However, I would like to use the spread operator on the members of a Foo (see updated code). – Christoph Bühler Apr 06 '20 at 18:20
  • Thanks, this helped me a lot. I will rethink my architecture. – Christoph Bühler Apr 06 '20 at 19:11
1

just adding my pinch of salt: I got this error when using smart quotes by mistake for my map keys (” instead of " - options+maj+quote on macOS). Replacing them with regular quotes did the trick

Daren Palmer
  • 142
  • 1
  • 10