The CreatorCon Call for Content is officially open! Get started here.

treycarroll
Giga Guru

After blogging my proposed u_GenericSet data type class a couple of days ago, I got the idea to finish out the thought with a post on a u_GenericMap data type.

I frequently use Javascript objects as a "poor man's" Dictionary/Associative Array/HashMap.   For brevity, I'm just going to refer to the structure as a Map.

This data type aggregates a list of key / value pairs.     It is probably one of the most intuitive data types to work with, and is very powerful.

We can use Javascript objects to create a Map.

var map = {};

map['AK'] = 1;

map['TX'] = 2;

map['CA'] = 3;

var texasRank = map['TX'];

gs.print('When it comes to square feet, Texas is #'+ texasRank);

This actually works very well.   It does exactly what we want to do with it, and there is no cognitive dissonance coming from improper accessors.   Of course, nothing is preventing us from doing something distinctly un-map-like like this

map['XX'] = {notLikeTheOthers:[1,2,3]};//What?   I thought that we were using string keys mapped to numeric values!   

If only there was a way to ensure that the key and value were of a type that was consistent with the intent of the data structure...

Meet u_GenericMap:

var u_GenericMap = Class.create();  

u_GenericMap.prototype = {  

         

      //Constructor.      

      //@param map is optional.   If provided, it must be of type u_GenericMap and its elements will be put() into the new Map.  

      initialize: function (keyType, valType, map) {  

 

              if (keyType == undefined || keyType == null || keyType == '') {  

                      throw "Mandatory constructor parameter 'keyType' was omitted when constructing object of type u_GenericMap";  

              }  

 

              if (!/^(number|string|object|boolean)$/i.test(keyType)) {  

                      throw "Constructor parameter 'keyType' must be number|string|object|boolean when constructing object of type u_GenericMap";  

              }  

 

              if (valType == undefined || valType == null || valType == '') {  

                      throw "Mandatory constructor parameter 'valType' was omitted when constructing object of type u_GenericMap";  

              }  

 

 

              if (!/^(number|string|object|boolean)$/i.test(valType)) {  

                      throw "Constructor parameter 'valType' must be number|string|object|boolean when constructing object of type u_GenericMap";  

              }  

 

              this.keyType = keyType.toLowerCase();  

              this.valType = valType.toLowerCase();  

              this.obj = {};  

              this.length = 0;  

 

              if (map && map.type == 'u_GenericMap') {  

                      var iterator = map.getIterator();  

                      while (iterator.next()) {  

                              this.obj[iterator.getCurrent().key] = iterator.getCurrent().value;  

                      }  

              }  

      },  

         

      //Adds an element to the map using he key/value params  

      put: function (key, val) {  

 

              var passedKeyType = (typeof key).toLowerCase();  

              var passedValType = (typeof val).toLowerCase();  

 

              if (passedKeyType != this.keyType) {  

                      throw "u_GenericMap was constructed to contain only keys of type:" + this.keyType + " Attempted to put element using key of type:" + passedKeyType;  

              }  

 

              if (passedValType != this.valType) {  

                      throw "u_GenericMap was constructed to contain only values of type:" + this.valType + " Attempted to put value of type:" + passedValType;  

              }                

 

              this.length++;  

              this.obj[key] = val;  

      },  

 

      //Returns true if the map contains this key  

      containsKey: function (key) {  

 

              var passedKeyType = (typeof key).toLowerCase();  

 

              if (passedKeyType != this.keyType) {  

                      throw "u_GenericMap was constructed to contain only keys of type:" + this.keyType + " Attempted to call containsKey with parameter of type:" + passedKeyType;  

              }  

 

              return this.obj.hasOwnProperty(key) ;  

      },  

 

      //Returns undefined if the key does not exist, otherwise returns the associated value  

      getValue: function (key) {  

 

              var passedKeyType = (typeof key).toLowerCase();  

 

              if (passedKeyType != this.keyType) {  

                      throw "u_GenericMap was constructed to contain only keys of type:" + this.keyType + " Attempted to call getValue with parameter of type:" + passedKeyType;  

              }  

 

              return this.obj[key];  

      },  

 

      //Remove an element at a key  

      remove: function (key) {  

 

              var passedKeyType = (typeof key).toLowerCase();  

 

              if (passedKeyType != this.keyType) {  

                      throw "u_GenericMap was constructed to contain only keys of type:" + this.keyType + " Attempted to call remove with parameter of type:" + passedKeyType;  

              }  

 

              if (!this.obj.hasOwnProperty(key)) {  

                      return false;  

              }  

 

              this.length--;  

              delete this.obj[key];  

 

              return true;  

      },  

 

      //Returns the size of the structure  

      size: function () { return this.length; },  

 

      //Determines if map is empty  

      isEmpty: function () {  

              return this.length == 0;  

      },  

 

      //Purge all elements  

      purgeAll: function () {  

              this.length = 0;  

              this.obj = {};  

      },  

 

      //Returns a JSON encoded string representation of the map  

      toString: function () {  

              try {//try client first.   This will throw if executing on the server  

                      if (window) {  

                              return JSON.stringify(this.obj);  

                      }  

              } catch (e) {//Server side  

                      try {  

                              return new JSON().encode(this.obj);  

                      } catch (e) {  

                              throw "u_GenericMap->toString() Error:" + e.message;  

                      }  

              }  

      },  

 

      //Returns an array of the keys  

      getKeys: function () {  

              var keys = [];  

              for (var k in this.obj) {  

                      keys.push(k);  

              }  

              return keys;  

      },  

 

      //Returns an array of the values  

      getValues: function () {  

              var vals = [];  

              for (var k in this.obj) {  

                      vals.push(this.obj[k]);  

              }  

              return vals;  

      },  

 

      //Returns an iterator with functions for next(), hasNext() and getCurrent().   getCurrent() returns an obj with keys: key, value  

      getIterator: function () {  

 

              var store = [];  

              var currentVal;  

              var currentIndex;  

                 

              for (var k in this.obj) {  

                      store.push({ 'key': k, 'value': this.obj[k] });  

              }  

              currentIndex = 0;                  

 

              //sets var "currentVal", returns true if there are more elements, false otherwise  

              function hasNext() {  

                      if (currentIndex < store.length) {  

                              currentVal = store[currentIndex];  

                              return true;  

                      } else {  

                              return false;  

                      }  

              }  

 

              //Returns the currentValue, set the var "currentVal", increments the internal index  

              function next() {  

                      currentVal = store[currentIndex];  

                      return store[currentIndex++];  

              }  

 

              //Returns an object with keys: key, value  

              function getCurrent() {  

                      return currentVal;  

              }  

              return { next: next, getCurrent: getCurrent, hasNext: hasNext };  

      },  

      type: 'u_GenericMap'  

}

You can use this class on both the client and server.

var m = new u_GenericMap('string', 'string');

m.put('Washington','1789');

m.put('Adams', '1797');

m.put('Jefferson','1801');

var it = m.getIterator();

while(it.next()){      

    gs.print(it.getCurrent().key+ ':' +       it.getCurrent().value);//gs.print only works on the server, but alert will work on the client

}

4 Comments