175

Basically, I'm trying to create an object of unique objects, a set. I had the brilliant idea of just using a JavaScript object with objects for the property names. Such as,

set[obj] = true;

This works, up to a point. It works great with string and numbers, but with other objects, they all seem to "hash" to the same value and access the same property. Is there some kind of way I can generate a unique hash value for an object? How do strings and numbers do it, can I override the same behavior?

Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
Boog
  • 3,744
  • 4
  • 28
  • 25
  • 34
    The reason the objects all 'hash' to the same value is because you have not overridden their toString methods. Since keys are required to be strings, the toString method is automatically called to obtain a valid key so all of your objects are converting to the same default string: "[object Object]". – alanning Feb 16 '12 at 17:45
  • 5
    `JSON.stringify(obj)` or `obj.toSource()` may work for you depending on the problem and target platform. – AnnanFay Jul 09 '13 at 23:07
  • 6
    @Annan JSON.stringify(obj) literally just converts the (whole) object into a string. So you would be basically just copying the object onto itself. This is pointless, a waste of space and not optimal. – Metalstorm Apr 26 '14 at 16:40
  • 1
    @Metalstorm True, which is why it depends what your problem is. When I found this question through google my final solution was calling toSource() on objects. Another method would just be to use a conventional hash on the source. – AnnanFay Apr 27 '14 at 17:53
  • @Annan, `toSource` don't work in Chrome btw – Pacerier Sep 18 '17 at 22:41
  • Well, if you're using node.js, https://www.npmjs.com/package/object-hash – Jason C Jun 10 '22 at 22:31

20 Answers20

80

If you want a hashCode() function like Java's in JavaScript, that is yours:

function hashCode(string){
    var hash = 0;
    for (var i = 0; i < string.length; i++) {
        var code = string.charCodeAt(i);
        hash = ((hash<<5)-hash)+code;
        hash = hash & hash; // Convert to 32bit integer
    }
    return hash;
}

That is the way of implementation in Java (bitwise operator).

Please note that hashCode could be positive and negative, and that's normal, see HashCode giving negative values. So, you could consider to use Math.abs() along with this function.

fregante
  • 29,050
  • 14
  • 119
  • 159
KimKha
  • 4,370
  • 1
  • 37
  • 45
  • 6
    this creates -hash, not perfect – qodeninja Sep 14 '12 at 00:15
  • 2
    @KimKha `char` is reserved word in JS and might cause some problems. Some other name would be better. – szeryf Feb 08 '13 at 19:28
  • 2
    @szeryf hashes are usually positive – qodeninja Feb 12 '13 at 16:45
  • 21
    @qodeninja says who? It's the first time I heard such a claim. Can you link to some source? Hashes are usually calculated using fixed-size integer arithmetic and bit operations, so getting positive or negative results is something to be expected. – szeryf Feb 14 '13 at 18:26
  • 7
    Picky, but... "if (this.length == 0) return hash;" is redundant :) And would personally change "character" to "code". – Metalstorm Jul 13 '13 at 23:43
  • 11
    @qodeninja and @szeryf: you just have to be careful how you use it. For instance, I tried to do `pickOne["helloo".hashCode() % 20]` for an array `pickOne` with 20 elements. I got `undefined` because the hash code is negative, so this is an example in which someone (me) implicitly assumed positive hash codes. – Jim Pivarski Feb 10 '14 at 20:20
  • 1
    When it returns a negative hash just use `Math.abs` –  Apr 14 '20 at 10:24
  • Warning: `"hello".hashCode()` is nearly the same as `"hellp".hashCode()`, it's typically what we *don't* want for a hash... It's sadly a bad recipe copied again and again in many SO answers... – Basj Jun 09 '20 at 15:21
  • @Basj it is expected that the hashCode is duplicated. Hence, using hashCode is not recommended in most of cases, but I wrote it here in case someone need that. So, make your choice. – KimKha Jun 10 '20 at 00:33
  • Also, please note that the answer based on Java's hashCode function, which may duplicate too, https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#hashCode%28%29 – KimKha Jun 10 '20 at 00:34
  • 1
    nobody mentioned this doesn't even answer the question – KTibow Mar 03 '23 at 01:28
37

JavaScript objects can only use strings as keys (anything else is converted to a string).

You could, alternatively, maintain an array which indexes the objects in question, and use its index string as a reference to the object. Something like this:

var ObjectReference = [];
ObjectReference.push(obj);

set['ObjectReference.' + ObjectReference.indexOf(obj)] = true;

Obviously it's a little verbose, but you could write a couple of methods that handle it and get and set all willy nilly.

Edit:

Your guess is fact -- this is defined behaviour in JavaScript -- specifically a toString conversion occurs meaning that you can can define your own toString function on the object that will be used as the property name. - olliej

This brings up another interesting point; you can define a toString method on the objects you want to hash, and that can form their hash identifier.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
eyelidlessness
  • 62,413
  • 11
  • 90
  • 94
  • another option would be to give each object a random value as it's hash - maybe a random number + total ticks - then have a set of functions to add/remove the object from the array. – Sugendran Oct 12 '08 at 00:52
  • 5
    This will fail if you add the same object twice. It will think that it is different. – Daniel X Moore May 24 '09 at 22:46
  • "This will fail if you add the same object twice. It will think that it is different." Good point. A solution could be to subclass Array for ObjectReference, hooking a duplicate check into push(). I don't have time to edit for this solution now, but I hope I'll remember to later. – eyelidlessness May 26 '09 at 13:24
  • 8
    I like this solution, because it does not need any additional properties in the object. But it's getting problematic, if you try to have a clean garbage collector. In your approach it will save the object although it's other references were already deleted. This can lead to problems in bigger applications. – Johnny Feb 19 '13 at 09:06
  • 44
    What's the point of hashing the objects if every time you refer to them you need a linear scan of an array? – Bordaigorl Nov 16 '14 at 11:36
  • I thought JavaScript objects can also use Symbols as keys...? – mesqueeb Jan 20 '20 at 07:09
  • This will cause a memory leak, since the references to these objects will always stay in that array and garbage collector will not clean the objects up as long as the array exists. – Dmitry Druganov Nov 26 '21 at 11:24
29

The easiest way to do this is to give each of your objects its own unique toString method:

(function() {
    var id = 0;

    /*global MyObject */
    MyObject = function() {
        this.objectId = '<#MyObject:' + (id++) + '>';
        this.toString= function() {
            return this.objectId;
        };
    };
})();

I had the same problem and this solved it perfectly for me with minimal fuss, and was a lot easier that re-implementing some fatty Java style Hashtable and adding equals() and hashCode() to your object classes. Just make sure that you don't also stick a string '<#MyObject:12> into your hash or it will wipe out the entry for your exiting object with that id.

Now all my hashes are totally chill. I also just posted a blog entry a few days ago about this exact topic.

Daniel X Moore
  • 14,637
  • 17
  • 80
  • 92
  • 30
    But this misses the whole point. Java has `equals()` and `hashCode()` so that two equivalent objects have the same hash value. Using the above method means that every instance of `MyObject` will have a unique string, which means you will have to keep a reference to that object to ever retrieve the correct value from the map. Having a key is meaningless, because it has nothing to do with the uniqueness of an object. A useful `toString()` function will need to be implemented for the specific type of object you are using as a key. – sethro Jan 17 '14 at 21:16
  • @sethro you can implement the `toString` for the objects such that it directly maps to an equivalence relation so that two objects create the same string iff they are considered "equal". – Daniel X Moore Jan 18 '14 at 20:16
  • 3
    Right, and that is the **only** correct way to use `toString()` to allow you to use an `Object` as a `Set`. I think I misunderstood your answer as trying to provide a generic solution to avoid writing a `toString()` equivalent of `equals()` or `hashCode()` on a case by case basis. – sethro Jan 23 '14 at 18:07
  • 3
    Dowvoted. This is not what a hashcode is, see my answers to: http://stackoverflow.com/a/14953738/524126 And a true implementation of a hashcode: http://stackoverflow.com/a/15868654/524126 – Metalstorm Apr 26 '14 at 16:37
  • 6
    @Metalstorm the question wasn't asking about a "true" hashcode, but rather how to successfully use an object as a set in JavaScript. – Daniel X Moore Apr 26 '14 at 17:42
  • @Daniel X Moore And to do that for objects you need to generate a hashcode, 2 objects (different reference) with the same internal state should not be in the set, and this is what a hashcode guards against. Normally this can be achieved more simply with a UUID. – Metalstorm Apr 26 '14 at 18:57
  • And what if your objects are DOM elements? Then you have to override a lot of stuff you would rather not override... – Michael May 27 '14 at 23:22
26

What you described is covered by Harmony WeakMaps, part of the ECMAScript 6 specification (next version of JavaScript). That is: a set where the keys can be anything (including undefined) and is non-enumerable.

This means it's impossible to get a reference to a value unless you have a direct reference to the key (any object!) that links to it. It's important for a bunch of engine implementation reasons relating to efficiency and garbage collection, but it's also super cool for in that it allows for new semantics like revokable access permissions and passing data without exposing the data sender.

From MDN:

var wm1 = new WeakMap(),
    wm2 = new WeakMap();
var o1 = {},
    o2 = function(){},
    o3 = window;

wm1.set(o1, 37);
wm1.set(o2, "azerty");
wm2.set(o1, o2); // A value can be anything, including an object or a function.
wm2.set(o3, undefined);
wm2.set(wm1, wm2); // Keys and values can be any objects. Even WeakMaps!

wm1.get(o2); // "azerty"
wm2.get(o2); // Undefined, because there is no value for o2 on wm2.
wm2.get(o3); // Undefined, because that is the set value.

wm1.has(o2); // True
wm2.has(o2); // False
wm2.has(o3); // True (even if the value itself is 'undefined').

wm1.has(o1);   // True
wm1.delete(o1);
wm1.has(o1);   // False

WeakMaps are available in current Firefox, Chrome and Edge. They're also supported in Node v7 , and in v6 with the --harmony-weak-maps flag.

salezica
  • 74,081
  • 25
  • 105
  • 166
  • 1
    What is the difference between these and `Map`? – smac89 Jul 11 '17 at 03:47
  • @smac89 WeakMap has limitations: 1) Takes only objects as keys 2) No size property 3) No iterator or forEach method 4) No clear method. Key is object - so when the object will be deleted from memory - data from WeakMap connected with this object will be deleted too. It's very useful when we want to keep the information, which should exist only while the object exists. So WeakMap has only methods: set, delete for write and get, has for read – Ekaterina Tokareva Aug 27 '17 at 12:35
  • This doesn't exactly work correctly... `var m = new Map();m.set({},"abc"); console.log(m.get({}) //=>undefined` It only works if you have the same variable you originally referenced in the set command. E.G. `var m = new Map();a={};m.set(a,"abc"); console.log(m.get(a) //=>undefined` – Sancarn Sep 04 '18 at 16:51
  • 1
    @Sancarn It doesn't have to be the same variable, but they have to point to the same object. In your first example you have two different objects, they look the same, but they have a different address. – Svish Nov 21 '19 at 10:47
18

The solution I chose is similar to Daniel's, but rather than use an object factory and override the toString, I explicitly add the hash to the object when it is first requested through a getHashCode function. A little messy, but better for my needs :)

Function.prototype.getHashCode = (function(id) {
    return function() {
        if (!this.hashCode) {
            this.hashCode = '<hash|#' + (id++) + '>';
        }
        return this.hashCode;
    }
}(0));
yckart
  • 32,460
  • 9
  • 122
  • 129
theGecko
  • 1,033
  • 12
  • 22
  • 9
    If you want to go this way, it's much better to set the hashCode via `Object.defineProperty` with `enumerable` set to `false`, so you won't crash any `for .. in` loops. – Sebastian Nowak Aug 07 '14 at 13:36
16

For my specific situation I only care about the equality of the object as far as keys and primitive values go. The solution that worked for me was converting the object to its JSON representation and using that as the hash. There are limitations such as order of key definition potentially being inconsistent; but like I said it worked for me because these objects were all being generated in one place.

var hashtable = {};

var myObject = {a:0,b:1,c:2};

var hash = JSON.stringify(myObject);
// '{"a":0,"b":1,"c":2}'

hashtable[hash] = myObject;
// {
//   '{"a":0,"b":1,"c":2}': myObject
// }
ijmacd
  • 346
  • 1
  • 3
  • 11
11

I put together a small JavaScript module a while ago to produce hashcodes for strings, objects, arrays, etc. (I just committed it to GitHub :) )

Usage:

Hashcode.value("stackoverflow")
// -2559914341
Hashcode.value({ 'site' : "stackoverflow" })
// -3579752159
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Metalstorm
  • 2,940
  • 3
  • 26
  • 22
  • Doesn't javascript's GC choke on circular references too? – Clayton Rabenda Jul 02 '13 at 21:43
  • @Ryan Long I might go as far as saying if you have circular references then you need to refactor your code ;) – Metalstorm Apr 25 '14 at 17:50
  • 13
    @Metalstorm "then you need to refactor your code" Are you kidding? Every DOM element parent and child pair constitutes a circular reference. – Chris Middleton Jan 04 '15 at 13:49
  • 9
    It does a poor job hashing objects that have numerical properties, returning the same value in many cases, i.e. `var hash1 = Hashcode.value({ a: 1, b: 2 }); var hash2 = Hashcode.value({ a: 2, b: 1 }); console.log(hash1, hash2);` will log `2867874173` `2867874173` – Julien Bérubé Mar 17 '15 at 16:35
9

In ECMAScript 6 there's now a Set that works how you'd like: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set

It's already available in the latest Chrome, FF, and IE11.

Daniel X Moore
  • 14,637
  • 17
  • 80
  • 92
  • 1
    This should be the top answer in 2016. If you use Babel, you can use Set per the ES6 spec and it will be polyfilled automatically in the ES5 output. https://babeljs.io/docs/learn-es2015/#map-set-weak-map-weak-set – atroberts20 Dec 02 '16 at 17:47
  • I don't believe this is a valid answer or solves the OP's problem because JS Sets don't allow objects to implement a hashcode function. Consider: `const set = new Set(); set.add({a:1}); set.add({a:1}); set.size // <--- this is 2, not 1` – lance.dolan Nov 07 '21 at 00:55
8

The JavaScript specification defines indexed property access as performing a toString conversion on the index name. For example,

myObject[myProperty] = ...;

is the same as

myObject[myProperty.toString()] = ...;

This is necessary as in JavaScript

myObject["someProperty"]

is the same as

myObject.someProperty

And yes, it makes me sad as well :-(

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
olliej
  • 35,755
  • 9
  • 58
  • 55
7

Based on the title, we can generate strong SHA hashes, in a browser context, it can be used to generate a unique hash from an object, an array of params, a string, or whatever.

async function H(m) {
  const msgUint8 = new TextEncoder().encode(m)                       
  const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8)          
  const hashArray = Array.from(new Uint8Array(hashBuffer))                    
  const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
  console.log(hashHex)
}

/* Examples ----------------------- */
H("An obscure ....")
H(JSON.stringify( {"hello" : "world"} ))
H(JSON.stringify( [54,51,54,47] ))

The above output in my browser, it should be equal for you too:

bf1cf3fe6975fe382ab392ec1dd42009380614be03d489f23601c11413cfca2b
93a23971a914e5eacbf0a8d25154cda309c3c1c72fbb9914d47c60f3cb681588
d2f209e194045604a3b15bdfd7502898a0e848e4603c5a818bd01da69c00ad19

Supported algos:

SHA-1 (but don't use this in cryptographic applications)
SHA-256
SHA-384
SHA-512

https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#Converting_a_digest_to_a_hex_string


However, for a simple FAST checksum hash function, made only for collision avoidance, see CRC32 (Content Redundancy Check)

JavaScript CRC32


You might also be interested by this similar method to generate HMAC codes via the web crypto api.

NVRM
  • 11,480
  • 1
  • 88
  • 87
  • 1
    Note that this will only work for HTTPS. The spec for WebCrypto explicitly states that webcrypto is `undefined` in insecure contexts (aka. http) – Gautham C. Mar 11 '21 at 17:01
  • This is really cool, but there's no support for it in older browsers. – dovidweisz Mar 17 '21 at 14:33
4

Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol

you can use Es6 symbol to create unique key and access object. Every symbol value returned from Symbol() is unique. A symbol value may be used as an identifier for object properties; this is the data type's only purpose.

var obj = {};

obj[Symbol('a')] = 'a';
obj[Symbol.for('b')] = 'b';
obj['c'] = 'c';
obj.d = 'd';
Khalid Azam
  • 1,615
  • 19
  • 17
  • 3
    Except that there isn't really a way to regenerate the Symbol let x = Symbol('a'); let y=Symbol('a');console.log(x===y); //returns false So Symbol does not work like a hash. – Richard Collette Nov 13 '19 at 22:13
2

Here's my simple solution that returns a unique integer.

function hashcode(obj) {
    var hc = 0;
    var chars = JSON.stringify(obj).replace(/\{|\"|\}|\:|,/g, '');
    var len = chars.length;
    for (var i = 0; i < len; i++) {
        // Bump 7 to larger prime number to increase uniqueness
        hc += (chars.charCodeAt(i) * 7);
    }
    return hc;
}
Timothy Perez
  • 20,154
  • 8
  • 51
  • 39
1

My solution introduces a static function for the global Object object.

(function() {
    var lastStorageId = 0;

    this.Object.hash = function(object) {
        var hash = object.__id;

        if (!hash)
             hash = object.__id = lastStorageId++;

        return '#' + hash;
    };
}());

I think this is more convenient with other object manipulating functions in JavaScript.

yckart
  • 32,460
  • 9
  • 122
  • 129
Johnny
  • 558
  • 8
  • 20
  • 1
    Objects with the same internal values will hash to different hashes, this is not what a hash(code) does. – Metalstorm Apr 25 '14 at 17:44
  • In JavaScript (and I think in almost every other language too) two objects created with the same internal values are still different objects, because the underlaying data type is represented by a new object instance each. http://jsfiddle.net/h4G9f/ – Johnny Apr 25 '14 at 19:56
  • 5
    Yes but that is not what a hashcode is for, hashcodes are used for checking equality of an objects state. Just like a hash, same inputs go in (variable values) same hash comes out. What you are looking for is an UUID (which is what your function provides). – Metalstorm Apr 25 '14 at 22:15
  • 1
    You're right. I missunderstand the question. Really to bad, that the accepted answer does not provide a good solution either. – Johnny Apr 26 '14 at 12:05
  • Taking your function as well, I would be inclined to so it more like this: http://jsfiddle.net/xVSsd/ Same result, shorter (LoC + chars), and probably a tiny tiny bit faster :) – Metalstorm Apr 26 '14 at 16:32
1

I will try to go a little deeper than other answers.

Even if JS had better hashing support it would not magically hash everything perfectly, in many cases you will have to define your own hash function. For example Java has good hashing support, but you still have to think and do some work.

One problem is with the term hash/hashcode ... there is cryptographic hashing and non-cryptographic hashing. The other problem, is you have to understand why hashing is useful and how it works.

When we talk about hashing in JavaScript or Java most of the time we are talking about non-cryptographic hashing, usually about hashing for hashmap/hashtable (unless we are working on authentication or passwords, which you could be doing server-side using NodeJS ...).

It depends on what data you have and what you want to achieve.

Your data has some natural "simple" uniqueness:

  • The hash of an integer is ... the integer, as it is unique, lucky you !
  • The hash of a string ... it depends on the string, if the string represents a unique identifier, you may consider it as a hash (so no hashing needed).
  • Anything which is indirectly pretty much a unique integer is the simplest case
  • This will respect: hashcode equal if objects are equal

Your data has some natural "composite" uniqueness:

  • For example with a person object, you may compute a hash using firstname, lastname, birthdate, ... see how Java does it: Good Hash Function for Strings, or use some other ID info that is cheap and unique enough for your usecase

You have no idea what your data will be:

  • Good luck ... you could serialize to string and hash it Java style, but that may be expensive if the string is large and it will not avoid collisions as well as say the hash of an integer (self).

There is no magically efficient hashing technique for unknown data, in some cases it is quite easy, in other cases you may have to think twice. So even if JavaScript/ECMAScript adds more support, there is no magic language solution for this problem.

In practice you need two things: enough uniqueness, enough speed

In addition to that it is great to have: "hashcode equal if objects are equal"

Christophe Roussy
  • 16,299
  • 4
  • 85
  • 85
1

I combined the answers from eyelidlessness and KimKha.

The following is an angularjs service and it supports numbers, strings, and objects.

exports.Hash = () => {
  let hashFunc;
  function stringHash(string, noType) {
    let hashString = string;
    if (!noType) {
      hashString = `string${string}`;
    }
    var hash = 0;
    for (var i = 0; i < hashString.length; i++) {
        var character = hashString.charCodeAt(i);
        hash = ((hash<<5)-hash)+character;
        hash = hash & hash; // Convert to 32bit integer
    }
    return hash;
  }

  function objectHash(obj, exclude) {
    if (exclude.indexOf(obj) > -1) {
      return undefined;
    }
    let hash = '';
    const keys = Object.keys(obj).sort();
    for (let index = 0; index < keys.length; index += 1) {
      const key = keys[index];
      const keyHash = hashFunc(key);
      const attrHash = hashFunc(obj[key], exclude);
      exclude.push(obj[key]);
      hash += stringHash(`object${keyHash}${attrHash}`, true);
    }
    return stringHash(hash, true);
  }

  function Hash(unkType, exclude) {
    let ex = exclude;
    if (ex === undefined) {
      ex = [];
    }
    if (!isNaN(unkType) && typeof unkType !== 'string') {
      return unkType;
    }
    switch (typeof unkType) {
      case 'object':
        return objectHash(unkType, ex);
      default:
        return stringHash(String(unkType));
    }
  }

  hashFunc = Hash;

  return Hash;
};

Example Usage:

Hash('hello world'), Hash('hello world') == Hash('hello world')
Hash({hello: 'hello world'}), Hash({hello: 'hello world'}) == Hash({hello: 'hello world'})
Hash({hello: 'hello world', goodbye: 'adios amigos'}), Hash({hello: 'hello world', goodbye: 'adios amigos'}) == Hash({goodbye: 'adios amigos', hello: 'hello world'})
Hash(['hello world']), Hash(['hello world']) == Hash(['hello world'])
Hash(1), Hash(1) == Hash(1)
Hash('1'), Hash('1') == Hash('1')

Output

432700947 true
-411117486 true
1725787021 true
-1585332251 true
1 true
-1881759168 true

Explanation

As you can see the heart of the service is the hash function created by KimKha.I have added types to the strings so that the sturucture of the object would also impact the final hash value.The keys are hashed to prevent array|object collisions.

eyelidlessness object comparision is used to prevent infinit recursion by self referencing objects.

Usage

I created this service so that I could have an error service that is accessed with objects. So that one service can register an error with a given object and another can determine if any errors were found.

ie

JsonValidation.js

ErrorSvc({id: 1, json: '{attr: "not-valid"}'}, 'Invalid Json Syntax - key not double quoted');

UserOfData.js

ErrorSvc({id: 1, json: '{attr: "not-valid"}'});

This would return:

['Invalid Json Syntax - key not double quoted']

While

ErrorSvc({id: 1, json: '{"attr": "not-valid"}'});

This would return

[]
0

In addition to eyelidlessness's answer, here is a function that returns a reproducible, unique ID for any object:

var uniqueIdList = [];
function getConstantUniqueIdFor(element) {
    // HACK, using a list results in O(n), but how do we hash e.g. a DOM node?
    if (uniqueIdList.indexOf(element) < 0) {
        uniqueIdList.push(element);
    }
    return uniqueIdList.indexOf(element);
}

As you can see it uses a list for look-up which is very inefficient, however that's the best I could find for now.

cburgmer
  • 2,150
  • 1
  • 24
  • 18
0

If you want to use objects as keys you need to overwrite their toString Method, as some already mentioned here. The hash functions that were used are all fine, but they only work for the same objects not for equal objects.

I've written a small library that creates hashes from objects, which you can easily use for this purpose. The objects can even have a different order, the hashes will be the same. Internally you can use different types for your hash (djb2, md5, sha1, sha256, sha512, ripemd160).

Here is a small example from the documentation:

var hash = require('es-hash');

// Save data in an object with an object as a key
Object.prototype.toString = function () {
    return '[object Object #'+hash(this)+']';
}

var foo = {};

foo[{bar: 'foo'}] = 'foo';

/*
 * Output:
 *  foo
 *  undefined
 */
console.log(foo[{bar: 'foo'}]);
console.log(foo[{}]);

The package can be used either in browser and in Node-Js.

Repository: https://bitbucket.org/tehrengruber/es-js-hash

darthmatch
  • 31
  • 4
0

If you truly want set behavior (I'm going by Java knowledge), then you will be hard pressed to find a solution in JavaScript. Most developers will recommend a unique key to represent each object, but this is unlike set, in that you can get two identical objects each with a unique key. The Java API does the work of checking for duplicate values by comparing hash code values, not keys, and since there is no hash code value representation of objects in JavaScript, it becomes almost impossible to do the same. Even the Prototype JS library admits this shortcoming, when it says:

"Hash can be thought of as an associative array, binding unique keys to values (which are not necessarily unique)..."

http://www.prototypejs.org/api/hash

0

If you want to have unique values in a lookup object you can do something like this:

Creating a lookup object

var lookup = {};

Setting up the hashcode function

function getHashCode(obj) {
    var hashCode = '';
    if (typeof obj !== 'object')
        return hashCode + obj;
    for (var prop in obj) // No hasOwnProperty needed
        hashCode += prop + getHashCode(obj[prop]); // Add key + value to the result string
    return hashCode;
}

Object

var key = getHashCode({ 1: 3, 3: 7 });
// key = '1337'
lookup[key] = true;

Array

var key = getHashCode([1, 3, 3, 7]);
// key = '01132337'
lookup[key] = true;

Other types

var key = getHashCode('StackOverflow');
// key = 'StackOverflow'
lookup[key] = true;

Final result

{ 1337: true, 01132337: true, StackOverflow: true }

Do note that getHashCode doesn't return any value when the object or array is empty

getHashCode([{},{},{}]);
// '012'
getHashCode([[],[],[]]);
// '012'

This is similar to @ijmacd solution only getHashCode doesn't has the JSON dependency.

A1rPun
  • 16,287
  • 7
  • 57
  • 90
0

Just use hidden secret property with the defineProperty enumerable: false

It work very fast:

  • The first read uniqueId: 1,257,500 ops/s
  • All others: 309,226,485 ops/s
var nextObjectId = 1
function getNextObjectId() {
    return nextObjectId++
}

var UNIQUE_ID_PROPERTY_NAME = '458d576952bc489ab45e98ac7f296fd9'
function getObjectUniqueId(object) {
    if (object == null) {
        return null
    }

    var id = object[UNIQUE_ID_PROPERTY_NAME]

    if (id != null) {
        return id
    }

    if (Object.isFrozen(object)) {
        return null
    }

    var uniqueId = getNextObjectId()
    Object.defineProperty(object, UNIQUE_ID_PROPERTY_NAME, {
        enumerable: false,
        configurable: false,
        writable: false,
        value: uniqueId,
    })

    return uniqueId
}
Nikolay Makhonin
  • 1,087
  • 12
  • 18