jsoid
v1.0.4
Published
Unique Object IDs for NodeJS (io.js at this time, really
Downloads
1
Readme
Table of Contents generated with DocToc
jsoid
jsoid
provides unique Object IDs for NodeJS (io.js at this time, really).
Motivation
When comparing classical JavaScript to a language like Python, there are (among many others) two things that JavaScript lacks and for which workarounds are sometimes necessary:
- Classical JS has no concept of a 'unique Object ID' (OID).
- Classical JS has no true Maps; you only get objects which accept strings as keys;
- Classical JS has no Weak Maps, meaning when you have to cache objects, you must always make sure to appropriately clear the cache when objects get out of scope.
- Because classical JS has only strings as keys, there are quite a few opportunities for
key collsions in case you're not careful (i.e.
x[ '123' ]
shadowsx[ 123 ]
,x[ 'NaN' ] shadows
x[ 0 / 0 ]` and so on). - Because classical JS has only strings as keys, it is somewhat hard to tack private attributes
such as
x.__FOOBAR_ID
to objects; in the general case, you should make those keys non-iterable in the general case (which has only been possible at all for a few years). - Because classical JS has no OIDs, it is hard to efficiently look into a given cache or somesuch
collection for the existence of a given object; an easy way to achieve that is to put
everything in an array and then query for
cache.indexOf( x )
, but that isn't bound to scale very well (and is prone to leak memory).
All this has quite recently changed; for NodeJS people, the most obvious change is
the release of io.js in January 2015, which out-of-the-box provides
ES6 Symbols, Maps, Sets, WeakMaps, generators / yield
, you name it.
For a quick-and-easy instruction how to install the latest
iojs
binary on your machine, have a look at my how-to.
It turns out that Symbols, Maps, Sets, WeakMaps have some amount of overlap in their
features that make all of the above tasks a lot easier. jsoid
uses ES6 WeakMaps
to implement efficient, memory-safe, bijective, non-obstrusive Object IDs that can readily
be used as string values for your favorite object collection. Because 'primitive'
values (numbers, strings, true, NaN, ...) are supported, you should never have to care
about the type of the values when using jsoid
.
Usage
Install as npm install --save jsoid
.
Be aware that this is not a mature module; i just slapped something together that seems
to work with a tiny test (run node --harmony jsoid/index.js
or, better still, iojs jsoid/index.js
;
if you get no output, the tests have passed).
From within your module, use
var jsoid = require( 'jsoid' )();
var d = {};
var e = {};
console.log( jsoid( d ) );
console.log( jsoid( e ) );
console.log( jsoid( 42 ) );
console.log( jsoid( 42 ) );
console.log( jsoid( 'helo' ) );
console.log( jsoid( 'helo!' ) );
to see the generated OIDs; you should get something like:
o#0
o#1
n#42
n#42
t#c6efaf27673d
t#8e95a23efc4e
Basically, that's a running ID for objects, and some kind of stringification for primitive values. Ah yes, and the IDs are all (short) strings, because with the more traditional numerical IDs, it's logically inconceivable to represent both all JS numbers and all the other possible values uniquely; only strings can do that, and only strings can act as object keys, so there's an added value there.
As it stands, each call to require( 'jsoid' )()
will return a new
instance of a OID-generating function; that function will generated unqiue IDs that
are consistent with the IDs of all other instances of the jsoid
function, but
since a simple counter is used in the case of objects, you must make sure to use only
one and the same jsoid
function for all your IDs. I'm willing to look into ways to fix that,
but that may not be worthwhile: when you can transfer an object from one point of
your application to another point, you can also transfer the jsoid
function. All
other solutions are more cumbersome.
One more point: i arbitrarily chose 12 digits of an sha-1
hash to represent all strings
because that method should be available everywhere, be good enough for the purpose,
and i think the number of digits should make collisions reasonably unlikely. And
i don't want a 1MB text to inadvertently get used as an object key in my code.
If you're worried you could ever run into a collsion like jsoid( text1 ) === jsoid( text2 )
with text1 !== text2
, read up on the math,
do the figures, and
complain / praise.
Source
The source is so short it fits into the readme:
@new_jsoid = new_jsoid = ( settings ) ->
throw new Error "settings not yet supported" if settings?
last_oid = -1
oid_map = new WeakMap()
R = {}
set = ( value ) ->
R = "o##{last_oid += +1}"
oid_map.set value, R
return R
R = ( value ) ->
return 'true' if value is true
return 'false' if value is false
return 'null' if value is null
return 'undefined' if value is undefined
if is_number value
### `isNan is broken as per
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isNaN;
however, we already know that `value` tests `true` for `util.isNumber`, so using `isNan` here should
be alright. ###
return 'nan' if isNaN value
return ( if value > 0 then "+infinity" else "-infinity" ) unless isFinite value
return "n##{value}"
return "t##{id_from_text value, 12}" if is_string value
return R if ( R = oid_map.get value )?
return set value
return R