Search code examples
dartdart-mirrors

How can you make dynamic getters/setters in dart


I am trying to recreate djangos QueryDict functionality and make a object that can be given a Map and it be a private variable in the object and getters/setters are used to pull from the map dynamically. I have managed to recreate the get() method of it but I am lost on dynamically getting value. Here is what I have so far:

class QueryMap {
  Map _data;

  QueryMap(Map this._data);

  dynamic get(String key, [var defaultValue]) {
    if(this._data.containsKey(key)) {
      return this._data[key];
    } else if(defaultValue) {
      return defaultValue;
    } else {
      return null;
    }
  }
}

Here is djangos page on how it works: https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.QueryDict.getitem


Solution

  • You can override noSuchMethod(emulating functions)

    @proxy
    class QueryMap {
      Map _data = new Map();
    
      QueryMap();
    
      noSuchMethod(Invocation invocation) {
        if (invocation.isGetter) {
          var ret = _data[invocation.memberName.toString()];
          if (ret != null) {
            return ret;
          } else {
            super.noSuchMethod(invocation);
          }
        }
        if (invocation.isSetter) {
          _data[invocation.memberName.toString().replaceAll('=', '')] =
              invocation.positionalArguments.first;
        } else {
          super.noSuchMethod(invocation);
        }
      }
    }
    void main() {
      QueryMap qaueryMap = new QueryMap();
      qaueryMap.greet = "Hello Dart!";
      print(qaueryMap.greet); //Hello Dart!
    }
    

    As noted by @PixelElephant with external map you have to use real method names as map keys:

    import 'dart:mirrors';
    @proxy
    class QueryMap {
      Map _data;
    
      QueryMap(this._data);
    
      noSuchMethod(Invocation invocation) {
        if (invocation.isGetter) {
          var ret = _data[MirrorSystem.getName(invocation.memberName)];
          if (ret != null) {
            return ret;
          } else {
            super.noSuchMethod(invocation);
          }
        }
        if (invocation.isSetter) {
          _data[MirrorSystem.getName(invocation.memberName).replaceAll('=', '')] =
              invocation.positionalArguments.first;
        } else {
          super.noSuchMethod(invocation);
        }
      }
    }
    void main() {
      Map myMap = new Map();
      myMap["color"] = "red";
      QueryMap qaueryMap = new QueryMap(myMap);
      qaueryMap.greet = "Hello Dart!";
      print(qaueryMap.greet); //Hello Dart!
      print(qaueryMap.color); //red
    }
    

    To avoid usage of mirrors, you can go with pattern matching on symbol's string serialization or transforming external map keys:

    @proxy
    class QueryMap {
      Map _data;
    
      QueryMap(Map data) {
        _data = new Map();
        data.forEach((k, v) => _data[new Symbol(k).toString()] = v);
      }
    
      noSuchMethod(Invocation invocation) {
        if (invocation.isGetter) {
          var ret = _data[invocation.memberName.toString()];
          if (ret != null) {
            return ret;
          } else {
            super.noSuchMethod(invocation);
          }
        }
        if (invocation.isSetter) {
          _data[invocation.memberName.toString().replaceAll('=', '')] =
              invocation.positionalArguments.first;
        } else {
          super.noSuchMethod(invocation);
        }
      }
    }
    void main() {
      Map myMap = new Map();
      myMap["color"] = "red";
      QueryMap qaueryMap = new QueryMap(myMap);
      qaueryMap.greet = "Hello Dart!";
      print(qaueryMap.greet); //Hello Dart!
      print(qaueryMap.color); //red
    }