typescriptflutterdartrxdart

Use Spread Operator on Object with Type Safety in Dart


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'.

Solution

  • 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!