jz87
jz87

Reputation: 9627

How to implement dynamic properties in Dart?

I would like to be able to back a dynamic property with a Map using a lookup in noSuchMethod(). However the latest changes makes the incoming property reference name unavailable. I can understand the minification scenario requiring us to use Symbols rather than Strings for names, but this makes implementing serializable dynamic properties difficult. Anyone have good ideas on how to approach this problem?

Upvotes: 12

Views: 7724

Answers (5)

Jason
Jason

Reputation: 933

You can set up an operator to do it.

class Point {
  int x;
  int y;
  String name;
  Point({required this.x, required this.y, required this.name});

  @override
  toString() {
    return {"name": name, "x": "$x", "y": "$y"}.toString();
  }

  void operator []=(Object key, Object value) {
    switch (key) {
      case "x":
        x = value as int;
        break;
      case "y":
        y = value as int;
        break;
      case "name":
        name = value as String;
        break;
      default:
    }
  }
}

void main() {
  var point = Point(x: 1, y: 1, name: "A");
  print(point.toString());

  point["x"] = 2;
  point["y"] = 3;

  print(point.toString());
}

Upvotes: 0

Donghua Liu
Donghua Liu

Reputation: 2186

Thanks for the solution of @Alexandre Ardhuin, I made some modification to make it runnable.

import 'dart:mirrors';

class object {
  final _properties = new Map<String, Object>();

  object();

  object.from(Map<String, Object> initial) {
    initial.entries.forEach((element) => _properties[element.key] = element.value);
  }

  noSuchMethod(Invocation invocation) {
    if (invocation.isAccessor) {
      final realName = MirrorSystem.getName(invocation.memberName);
      if (invocation.isSetter) {
        // for setter realname looks like "prop=" so we remove the "="
        final name = realName.substring(0, realName.length - 1);
        _properties[name] = invocation.positionalArguments.first;
        return;
      } else {
        return _properties[realName];
      }
    }
    return super.noSuchMethod(invocation);
  }

  @override
  String toString() {
    return _properties.toString();
  }
}

main() {
  // we can't use var or object type here, because analysis will consider
  // https://dart.dev/tools/diagnostic-messages#undefined_setter
  // The setter 'i' isn't defined for the type 'object'
  // So dynamic is required here!
  dynamic a = object.from({'a': 123, 'b': 234});
  a.i = 151;
  print(a); // print {a: 123, b: 234, i: 151}
  try {
    a.someMethod(); // throws NoSuchMethodError
  } catch (e) {
    print(e);
  }
}

Upvotes: 1

Greg Lowe
Greg Lowe

Reputation: 16281

You could do something like this:

import 'dart:json' as json;

main() {
    var t = new Thingy();
    print(t.bob());
    print(t.jim());
    print(json.stringify(t));
}

class Thingy {
    Thingy() {
        _map[const Symbol('bob')] = "blah";
        _map[const Symbol('jim')] = "oi";
    }

    final Map<Symbol, String> _map = new Map<Symbol, String>();

    noSuchMethod(Invocation invocation) {
        return _map[invocation.memberName];
    }

    toJson() => {
        'bob': _map[const Symbol('bob')],
        'jim': _map[const Symbol('jim')]};
}

Update - dynamic example:

import 'dart:json' as json;

main() {
    var t = new Thingy();
    t.add('bob', 'blah');
    t.add('jim', 42);
    print(t.bob());
    print(t.jim());
    print(json.stringify(t));
}

class Thingy {
    final Map<Symbol, String> _keys = new Map<Symbol, String>();
    final Map<Symbol, dynamic> _values = new Map<Symbol, dynamic>();

    add(String key, dynamic value) {
        _keys[new Symbol(key)] = key;
        _values[new Symbol(key)] = value;
    }

    noSuchMethod(Invocation invocation) {
        return _values[invocation.memberName];
    }

    toJson() {
        var map = new Map<String, dynamic>();
        _keys.forEach((symbol, name) => map[name] = _values[symbol]);
        return map;
    }
}

Upvotes: 1

Alexandre Ardhuin
Alexandre Ardhuin

Reputation: 76233

You can access the original name with MirrorSystem.getName(symbol)

So a dynamic class could look like :

import 'dart:mirrors';

class A {
  final _properties = new Map<String, Object>();

  noSuchMethod(Invocation invocation) {
    if (invocation.isAccessor) {
      final realName = MirrorSystem.getName(invocation.memberName);
      if (invocation.isSetter) {
        // for setter realname looks like "prop=" so we remove the "="
        final name = realName.substring(0, realName.length - 1);
        _properties[name] = invocation.positionalArguments.first;
        return;
      } else {
        return _properties[realName];
      }
    }
    return super.noSuchMethod(invocation);
  }
}

main() {
  final a = new A();
  a.i = 151;
  print(a.i); // print 151
  a.someMethod(); // throws
}

Upvotes: 13

Ladicek
Ladicek

Reputation: 6587

If you only need "dynamic properties", it should be enough to use Symbols as keys in the Map. If you also want to serialize that map, then you need to keep track of the original String names and use those for serialization. When deserializing, you'd have to create new Symbols from those Strings.

Note that all these scenarios (and basically everything that involves new Symbol) require a compiler to create a mapping of original names to the minified ones and put this mapping into the program, which of course makes it bigger.

Upvotes: 0

Related Questions