7

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

Günter Zöchbauer
  • 490,478
  • 163
  • 1,733
  • 1,404
DigitalFiz
  • 494
  • 2
  • 15

1 Answers1

10

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
}
JAre
  • 4,416
  • 2
  • 21
  • 38
  • Note that `memberName` is a `Symbol` and you should call `MirrorSystem.getName` on it to get a string which you can use as a key (instead of toString). – Pixel Elephant Jun 17 '14 at 02:18
  • @PixelElephant I tried to avoid using mirrors in any shape or form because they can affect performance. – JAre Jun 17 '14 at 02:20
  • 1
    Yes, but they are needed here. Your code only works because you both set and get using noSuchMethod. If you had used an already created map e.g. `Map map = {'greet': 'Hello Dart'};`, the code would fail since it is trying to get `Symbol('greet')` and not just `greet`. – Pixel Elephant Jun 17 '14 at 02:25
  • @PixelElephant Thank it's a valid point. I forgot that it uses external map. – JAre Jun 17 '14 at 02:26
  • This is pretty genius! So I am guessing the last method will work on flutter too where mirrors are not allowed? Also does anyone have any idea of the performance hit of using this? It really helps reduce a lot of boiler plate in our codebase so I am tempted to use it – Gerard Feb 25 '21 at 13:33