15

Do the keys of JavaScript associative arrays need to be strings, or can they be any object?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
morgancodes
  • 25,055
  • 38
  • 135
  • 187

5 Answers5

16

There are no native associative arrays in JavaScript, only objects. Objects have properties. The names of properties are always strings: even the numeric indices of arrays will be converted to strings before the 'array magic' happens.

If you're looking for associative arrays with arbitrary keys, look here.

Community
  • 1
  • 1
Christoph
  • 164,997
  • 36
  • 182
  • 240
  • And... what does this mean, exactly? Property names typically have stricter limitations than just strings: they must be valid identifiers. Does this limitation stand for associative array keys? – ivan_pozdeev May 31 '18 at 23:36
2

I've implemented JavaScript HashMap which code can be obtained from http://github.com/lambder/HashMapJS/tree/master

The keys and values can be arbitrary JavaScript objects. There aren't any requirements on objects used as keys or values.

The mechanism is trivial. For every key there is generated a unique id (per HashMap instance). That id is injected to the key object under a high unlikely to collide field name ;)

That id is then used to keying in the underlying baking standard JavaScript association object.

Here is the code:

/*
 =====================================================================
 @license MIT
 @author Lambder
 @copyright 2009 Lambder.
 @end
 =====================================================================
 */
var HashMap = function() {
  this.initialize();
}

HashMap.prototype = {
  hashkey_prefix: "<#HashMapHashkeyPerfix>",
  hashcode_field: "<#HashMapHashkeyPerfix>",

  initialize: function() {
    this.backing_hash = {};
    this.code = 0;
  },

  /*
   Maps value to key, returning the previous association
   */
  put: function(key, value) {
    var prev;
    if (key && value) {
      var hashCode = key[this.hashcode_field];
      if (hashCode) {
        prev = this.backing_hash[hashCode];
      } else {
        this.code += 1;
        hashCode = this.hashkey_prefix + this.code;
        key[this.hashcode_field] = hashCode;
      }
      this.backing_hash[hashCode] = value;
    }
    return prev;
  },

  /*
   Returns value associated with given key
   */
  get: function(key) {
    var value;
    if (key) {
      var hashCode = key[this.hashcode_field];
      if (hashCode) {
        value = this.backing_hash[hashCode];
      }
    }
    return value;
  },
  /*
   Deletes association by given key.
   Returns true if the association existed, false otherwise
   */
  del: function(key) {
    var success = false;
    if (key) {
      var hashCode = key[this.hashcode_field];
      if (hashCode) {
        var prev = this.backing_hash[hashCode];
        this.backing_hash[hashCode] = undefined;
        if(prev !== undefined)
          success = true;
      }
    }
    return success;
  }
}

//// Usage

// Creation

var my_map = new HashMap();

// Insertion

var a_key = {};
var a_value = {struct: "structA"};
var b_key = {};
var b_value = {struct: "structB"};
var c_key = {};
var c_value = {struct: "structC"};

my_map.put(a_key, a_value);
my_map.put(b_key, b_value);
var prev_b = my_map.put(b_key, c_value);

// Retrieval

if(my_map.get(a_key) !== a_value){
  throw("fail1")
}
if(my_map.get(b_key) !== c_value){
  throw("fail2")
}
if(prev_b !== b_value){
  throw("fail3")
}

// Deletion

var a_existed = my_map.del(a_key);
var c_existed = my_map.del(c_key);
var a2_existed = my_map.del(a_key);

if(a_existed !== true){
  throw("fail4")
}
if(c_existed !== false){
  throw("fail5")
}
if(a2_existed !== false){
  throw("fail6")
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Lambder
  • 2,953
  • 1
  • 26
  • 20
1

Are you talking about JavaScript objects (JSON)?

The specification says that the keys should be strings.

But the JavaScript interpreter allows both {"key": "val"} and {key: "val"}.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Luca Matteis
  • 29,161
  • 19
  • 114
  • 169
  • I'm wondering if I can have a structure that works like a map in java, where keys can be any object (even another array). It doesn't have to be declared in json, it can be declared like: myarray[myObject] = value. – morgancodes Feb 04 '09 at 19:16
  • How can the key be an object, it's an identifier... maybe it should be myarray[value] = myObject; – Luca Matteis Feb 04 '09 at 19:18
  • 1
    @sktrdie Imagine this: { [ 'some', 'object', 'as', 'key' ]: 'value' } Invalid JSON, of course, but the question is whether it's valid in JS. – ephemient Feb 04 '09 at 19:38
0

Building on Lambder's idea, I've implemented a small DataStructures library.

I've tested it a little and everything seems to work.

It also automatically assigns a unique id to each HashTable/HashSet used to uniquely identify the object's key property.

var DataStructure = {};

DataStructure.init = function(){
    DataStructure.initHashables();
    delete DataStructure.initHashables;
}

DataStructure.initHashables = function(){
    var objectHashableIndexer = new DataStructure.Indexer();

    DataStructure.Hashable = function(){
        var self = this;

        // Constant
        //
        //
        const ERROR_KEY_DOES_NOT_EXIST = "Key doesn't exists in Hashable when trying to pop. Associated Key Object and Hashable logged to console.";
        const HASH_MAP_KEY_PROPERTY_BASE = "DATA_STRUCTURE_HASH_MAP_KEY_PROPERTY_";

        // Attributes
        //
        //
        var tableNumber = objectHashableIndexer.getIndex();
        var tableKeyProperty = HASH_MAP_KEY_PROPERTY_BASE + tableNumber.toString();
        self.tableKeyProperty = tableKeyProperty;
        var indexer = new DataStructure.Indexer();
        var data = {};
        self.data = data;

        // Methods
        //
        //
        self.getObjectKey = function(){
            return indexer.getIndex().toString();
        }

        self.putBackObjectKey = function(index){
            indexer.putBackIndex(parseInt(index));
        }

        var getObjectKey = self.getObjectKey;
        var putBackObjectKey = self.putBackObjectKey;

        self.exists = function(key){
            if (!(tableKeyProperty in key))
                return false;

            var realKey = key[tableKeyProperty];

            if (!(realKey in data))
                return false;

            return true;
        }

        self.pop = function(key){
            if (!self.exists(key)){
                console.log(key);
                console.log(self);
                throw ERROR_KEY_DOES_NOT_EXIST;
            }
            else{
                var realKey = key[tableKeyProperty];
                delete key[tableKeyProperty];
                delete data[realKey];
                putBackObjectKey(realKey);
            }
        }

        self.destroy = function(){
            objectHashableIndexer.putBackIndex(tableNumber);
            delete self;
        }

    }

    /*
        Class DataStructure.ObjectHashMap
            Purpose: Provides a way to hash arbitrary objects to values.

            Prototype Arguments:

            Attributes:

            Methods:

            Notes:
                Should call inherited method destroy() when done with table to preserve indexes
    */
    DataStructure.ObjectHashMap = function(){
        DataStructure.Hashable.call(this);
        var self = this;

        // Constant
        //
        //
        const ERROR_KEY_EXISTS = "Key already exists in ObjectHashMap when trying to push. Associated Key Object and ObjectHashMap logged to console.";
        const ERROR_KEY_DOES_NOT_EXIST = "Key doesn't exists in ObjectHashMap when trying to getValue. Associated Key Object and ObjectHashMap logged to console.";

        // Attributes
        //
        //

        var tableKeyProperty;
        var data;

        // Initialization
        //
        //
        self.init = function(){
            self.privatize();
            delete self.privatize;
        }

        self.privatize = function(){
            tableKeyProperty = self.tableKeyProperty;
            delete self.tableKeyProperty;

            getObjectKey = self.getObjectKey;
            delete self.getObjectKey;

            putBackObjectKey = self.putBackObjectKey;
            delete self.putBackObjectKey;

            data = self.data;
            delete self.data;
        }

        // Methods
        //
        //
        var getObjectKey;
        var putBackObjectKey;

        self.push = function(key, value){
            if (self.exists(key)){
                console.log(key);
                console.log(self);
                throw ERROR_KEY_EXISTS;
            }
            else{
                var realKey = getObjectKey();
                key[tableKeyProperty] = realKey;
                data[realKey] = value;
            }
        }

        self.getValue = function(key){
            if(!self.exists(key)){
                console.log(key);
                console.log(self);
                throw ERROR_KEY_DOES_NOT_EXIST;
            }
            else{
                var realKey = key[tableKeyProperty];
                return data[realKey];
            }
        }

        self.init();
        delete self.init;
    }

    /*
        Class DataStructure.ObjectHashSet
            Purpose: Provides a way to store arbitrary objects and check that they exist.

            Prototype Arguments:

            Attributes:

            Methods:

            Notes:
                Should call inherited method destroy() when done with table to preserve indexes
    */
    DataStructure.ObjectHashSet = function(){
        DataStructure.Hashable.call(this);
        var self = this;

        // Constant
        //
        //
        const ERROR_KEY_EXISTS = "Key already exists in ObjectHashSet when trying to push. Associated Key Object and ObjectHashSet logged to console.";
        const ERROR_KEY_DOES_NOT_EXIST = "Key doesn't exists in ObjectHashSet when trying to getValue. Associated Key Object and ObjectHashSet logged to console.";

        // Attributes
        //
        //

        var tableKeyProperty;
        var data;

        // Initialization
        //
        //
        self.init = function(){
            self.privatize();
            delete self.privatize;
        }

        self.privatize = function(){
            tableKeyProperty = self.tableKeyProperty;
            delete self.tableKeyProperty;

            getObjectKey = self.getObjectKey;
            delete self.getObjectKey;

            putBackObjectKey = self.putBackObjectKey;
            delete self.putBackObjectKey;

            data = self.data;
            delete self.data;
        }

        // Methods
        //
        //
        var getObjectKey;
        var putBackObjectKey;

        self.push = function(key){
            if (self.exists(key)){
                console.log(key);
                console.log(self);
                throw ERROR_KEY_EXISTS;
            }
            else{
                var realKey = getObjectKey();
                key[tableKeyProperty] = realKey;
                data[realKey] = "";
            }
        }

        self.init();
        delete self.init;
    }

}


DataStructure.Indexer = function(){
    var self = this;

    // Constant
    //
    //
    const DEFAULT_SIZE = 1000;

    // Attributes
    //
    //
    var nextIndex = 0;
    var availableIndicies = 0;
    var freeIndicies = [];

    // Initialization
    //
    //
    self.init = function(){
        freeIndicies.length = DEFAULT_SIZE;
    }

    // Methods
    //
    //
    self.getIndex = function(){
        var index = 0;

        if (availableIndicies === 0){
            index = nextIndex;
            ++nextIndex;
        }
        else{
            --availableIndicies;
            index = freeIndicies[availableIndicies];
        }

        return index;
    }

    self.putBackIndex = function(index){
        if (availableIndicies === freeIndicies.length)
            freeIndicies.push(index);
        else
            freeIndicies[availableIndicies] = index;

        ++availableIndicies;
    }

    self.init();
    delete self.init;
}

DataStructure.init();
delete DataStructure.init;
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
HumbleWebDev
  • 555
  • 4
  • 20
0

It depends on what you mean by "associative arrays". There is nothing called "associative arrays" in JavaScript, there are objects and there are maps.

Objects are the ones that can be accessed using the [] notation, for example foo["bar"], and there the keys have to be strings, as Christoph's answer explains.

There are also maps, which can have any object as keys. However, to access them, you can't use [] as for objects, you must use the get and set methods. Here is an example how to use them:

let myMap = new Map();    // Create the map
myMap.set("key", "value");    // To set a value, use the set method.
                            // The first argument is the key, the second one is the value.
myMap.set(Math, "bar");    // You can really use any object as key
myMap.set(console.log, "hello");    // Including functions
myMap.set(document.body, "world");    // And even DOM elements

// To get the value associated to a key, use the get method
console.log(myMap.get(Math));    // "bar"
console.log(myMap.get(document.body));    // "world"

In this example I used built-in objects as keys in order to avoid cluttering the example with defining new objects to use as keys, but it's of course possible to use your own objects as keys.

Be careful, however, not to use [] to access elements of a map. Doing myMap[whatever] is valid code so it won't throw an error, but it won't work as expected:

// Don't do this
myMap[Math] = 3;
myMap["[object Math]"] = 4;
console.log(myMap[Math]);    //4
console.log(myMap.get(Math));    // 'undefined'

// Do this instead
myMap.set(Math, 3);
myMap.set("[object Math]", 4);
console.log(myMap.get(Math));    //3

To learn more about maps, see Map.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Donald Duck
  • 8,409
  • 22
  • 75
  • 99