
An Object is not a Hash - philfreo
http://www.devthought.com/2012/01/18/an-object-is-not-a-hash/
======
andrewvc
The fact is, it's 2012. We all appreciate V8 making the web fast. It's stuff
like this, however, that makes me question the sanity of those _still_
promoting node.js

The fact of the matter is that JS is bad at all the things a programming
language is supposed to be good at. JS can't even perform integer math. It's
also fairly alone in being async by default (and no, comparisons to erlang do
not count).

Does JS work? Sure, but believing JS is good is a special kind of delusion. We
have decades of language research to work with, and once again we see the pull
of the lowest common denominator.

~~~
ender7
The people waiting for the programming community to suddenly wake up and
realize how wrong they were about Javascript all these years remind me of all
the people still waiting for Castro to die.

By the time it happens, it won't actually mean anything because something
else, something equally despised and impure will have taken its place.

Perhaps a better question might be _why_ are such languages more popular than
their more ideologically pure cousins? What need are they filling that
"better" languages cannot? Be careful if your answer is "they're beginner's
tools for mediocre programmers" -- the JS community is home to some extremely
talented programmers who are all there by choice alone.

~~~
blub
Trying to make one's tools better is always worth it. Typescript is to me the
most exciting thing to have happened to JS, better than all the so called
libraries that have to be declared inside a function (!!) so that the scope
doesn't get completely screwed up.

The JS community is not an argument for JS I feel, because it does not have a
track record of providing reliable software. Most libraries are new and a lot
of devs are very lax with security or code robustness. If you use a
library/tool you're not sure what you're getting - see the semicolon situation
or the npm directory deletion.

~~~
lloeki
> _the so called libraries that have to be declared inside a function (!!)_

This scope trick (and really, the whole JS language situation) always reminded
me of the "closures vs. objects" koan:

[http://people.csail.mit.edu/gregs/ll1-discuss-archive-
html/m...](http://people.csail.mit.edu/gregs/ll1-discuss-archive-
html/msg03277.html)

~~~
Avshalom
as a note this works in JS too.

    
    
      var mariner= {};
    
      mariner.new = function(){
          var self = {};
          var maxhp = 200;
          var hp = maxhp;
          
          self.heal = function(deltahp) {
              if (hp + deltahp < maxhp){
                  hp = hp + deltahp;
              } else {
                  hp = maxhp;
              }
          }
          self.sethp = function(newhp) {
              if (newhp < maxhp){
                  hp = newhp;
              } else {
                  hp = maxhp;
              }
          }
          self.gethp = function(){
              return hp;
          }
          
          return self; 
      }
    
      var m1 = mariner.new();
      m1.sethp(100);
      m1.heal(13);
      alert(m1.gethp());
    

(shamelessly stolen from <http://lua-
users.org/wiki/ObjectOrientationClosureApproach>)

------
philfreo
As a shortcut, in underscore.js you can:

    
    
      _.has(object, key)
    

or

    
    
      _(object).has(key)
    

<http://underscorejs.org/#has>

~~~
alanh
I love that the underscore doc for `has` links to the submitted article.

------
mercurial
It's a pretty good illustration of why associative arrays in Javascript are a
design failure, by conflating different notions in the same implementations:

\- objects are not hashes

\- hashes are not arrays

It sounds like somebody reading the classic quote by Saint Exupéry, "A
designer knows he has achieved perfection not when there is nothing left to
add, but when there is nothing left to take away.", and concluding: "Well, if
I mash all the elements to a paste, there won't be anything to take away."

------
jtchang
Is there some type of injection vulnerability here?

Is it possible to attack a node.js server that creates objects via some API
and give it a name that overwrites an internal javascript property? Your
content would be the javascript injected code that might get run when the
property is used internally?

~~~
drostie
That's what the original poster is saying, yes. JS does not support a formal
separation between the 'attributes' and 'keys' of a hash, so it's a security
risk waiting to happen.

In some cases the JS approach is very nice and useful: in Python you see gobs
and gobs of classes used to hold data structures, mostly because people don't
seem to like dicts in general. I remember a talk, but not the speaker or title
of it, where it was pointed out that your Python class is probably a dict plus
a function if it has exactly two methods, one of which is called __init__.
Python being what it is, you may also extend Python to do what JS does, but
slightly more intelligently:

    
    
        class obj(dict):
            def __init__(self, vals={}):
                for key in vals:
                    self[key] = vals[key]
            def __setattr__(self, name, value):
                self[name] = value
            def __getattribute__(self, name):
                try:
                    return dict.__getattribute__(self, name)
                except AttributeError:
                    if name in self:
                        return self[name]
                    else:
                        raise AttributeError("'obj' has no key '%s'" % name)
    

This lets you use dotted setters/getters with Python without accidentally
having x['keys'] = 1 override the obj.keys() method (which it inherits from
dict). It's a serious problem in real Python code I've read and written; you
create 'classes' which are only instantiated once (and are therefore
semantically dicts). So the JS model really has some advantages for easy,
clear namespacing (and packaging and enumeration) of identifiers, but yes,
there is a potential security question if you allow people to set or get
arbitrary JS attributes.

~~~
inglesp
I think you're thinking of Jack Diederich's talk "Stop Writing Classes". You
can (and should!) watch it online here:
<http://www.youtube.com/watch?v=o9pEzgHorH0>

~~~
drostie
Thank you, it's much appreciated.

------
Stratoscope
Aw gee, you want a real hash in JavaScript, how hard can it be? Here's a quick
and dirty one I whipped up in five minutes. It worked the first time with only
one bug - I forgot to remove the prefix in the forEach method at first.

    
    
      function Hash() {
          this.hash = {};
      }
      
      Hash.prefix = '#hash#prefix#_';
      
      Hash.prototype.get = function( key ) {
          return this.hash[ Hash.prefix + key ];
      };
      
      Hash.prototype.set = function( key, value ) {
          return this.hash[ Hash.prefix + key ] = value;
      };
      
      Hash.prototype.del = function( key ) {
          delete this.hash[ Hash.prefix + key ];
      };
      
      Hash.prototype.forEach = function( callback ) {
          for( var key in this.hash ) {
              if( key.indexOf(Hash.prefix) == 0 ) {
                  callback.call( this, this.hash[key], key.slice(Hash.prefix.length) );
              }
          }
      };
      
      // Q&D test
      
      // Let's try to break things by extending Object.prototype!
      Object.prototype.spindizzy = true;
      
      var hash = new Hash;
      console.log( hash.get( 'a' ), hash.get( 'b' ) );
      hash.set( 'a', 'AA' );
      console.log( hash.get( 'a' ), hash.get( 'b' ) );
      hash.set( 'b', 'BB' );
      hash.forEach( function( value, key ) {
          console.log( key, value );
      });
      hash.del( 'a' );
      console.log( hash.get( 'a' ), hash.get( 'b' ) );
      hash.del( 'b' );
      console.log( hash.get( 'a' ), hash.get( 'b' ) );
    

I'm sure I missed something, but this doesn't seem like rocket science.

Of course, you do lose the syntactic sugar of [] dereferencing so it looks
like part of the language, but life is full of tradeoffs! :-)

And I suppose there is still a way to break it, if you extend Object.prototype
with something that happens to match Hash.prefix. I guess you could use
hasOwnPrototype to prevent this. Ah well, maybe I do have a bug after all, but
at least it's a good start.

~~~
anonymous
Here, let me make this easier for you

    
    
      > var hash = []
      undefined
      > hash
      []
      > hash['a']
      undefined
      > hash['a'] = 33
      33
      > hash
      [ a: 33 ]
      > hash[3] = 3
      3
      > hash
      [ , , , 3, a: 33 ]
      > hash[2]
      undefined
      > hash[hash]
      undefined
      > hash[hash] = hash
      [ ,
        ,
        ,
        3,
        a: 33,
        ',,,3': [Circular] ]
      > hash[0] = 'zero'
      'zero'
      > hash['0']
      'zero'
    
    

JavaScript already has a perfectly well functioning associative array that can
store whatever keys you like with the only gotcha being that strings
containing only numbers act like numbers, when used as keys.

~~~
Stratoscope
That has a couple of problems. Try this:

    
    
      > var hash = [];
      undefined
      > hash
      []
      > hash['length']
      0
      > // Hey! That should have been undefined, not 0!
      > hash['length'] = 'test'
      RangeError: Invalid array length
      > // And I can't set it either!
    

Also, the part where you're doing hash[hash] doesn't do at all what you think.
When you write this code:

    
    
      hash[hash] = whatever;
    

What you're really doing is:

    
    
      hash[ hash.toString() ] = whatever;
    

And with hash being an array, hash.toString() is highly browser dependent. In
Chrome, if you don't have any numeric array keys, then hash.toString() is ""
regardless of your non-numeric keys (at least in the version I'm testing). So
you may well be really doing this in Chrome:

    
    
      hash[""] = whatever;  // Oops
    

If you do want to use a native JavaScript type as a hash, the one to use is
Object, not Array. Simply change the first line of your test to:

    
    
      var hash = {};
    

and go from there.

This still doesn't solve the name collisions, but is plenty useful anyway.

If you do want a more fool-proof hash that lets you use any string as a key
without fear, I'm pretty sure you need to write some code like the Hash class
I posted. You still couldn't do the hash[hash] - or hash.get(hash) - because
of the dependence on .toString(), but any string key would work. You could
even get fancy and give the Hash class some kind of useful .toString() method
so you could use hash[hash] - having your own code here opens up those kinds
of possibilities.

~~~
anonymous
You're right, I was too quick to reply. I'm not really a javascript
programmer, I figured the Array type would have different semantics on the []
operator than a plain Object.

~~~
masklinn
It doesn't. Which is why you can use it in the completely broken manner you
did.

------
mcot2
It sounds like this will all be fixed when js gets proper map types in ES6.

~~~
just2n
When I first saw this post, it didn't include the following snippet:

    
    
        Object.create(null);
    

I disagreed adamantly that a hash with pre-defined names is not a hash. The
language already provides a means to get a hash without the pre-defined names,
and any clever person can hack around naming collisions regardless. He now
notes these things at the end of the article, but then the entire argument
doesn't imply that a hash is not a hash. He does a good job pointing out a
mistake ALMOST EVERYONE makes, and I applaud that, but the title is horribly
misleading, even when given the proper context.

Also, @mcot2, I didn't intend to post as a reply to you, but since I have, to
answer your question, collections (Maps, Sets, and WeakMaps) are available in
V8 and consequently Node already (via --harmony flags). Because the
implementations of these with get/set is not tied to the object's properties,
these don't have the same problem.

~~~
comex
Isn't the whole point of Maps that their get/set methods are completely
independent from object properties?

~~~
comex
(for the record: parent edited his post to address this.)

~~~
just2n
I'm sorry about that. I went back later to edit in a comment to make it clear
that I had incorporated your point into my response but the edit had expired.

------
cousin_it

        if (!posts.hasOwnProperty(req.body.slug)) {
          posts[req.body.slug] = new Post(req.body);
          res.send(200);
        }
    

Maybe I'm missing something, but doesn't that code also happily assign to
properties like "constructor", not just "hasOwnProperty"?

------
mun2mun
I think this problem can be easily solved by encapsulating access/creation
methods of posts objects in a object say postStore where posts variable is
private property and the object have savePost(slug) and getPost(slug) methods
where access/creation methods are defined by yourself. If posts object is that
much mission critical than it should not be exposed to outer world.

~~~
kansface
Instead of writing code around the problem, its better to just use a safe
reference (ie, use Underscore's has method which will call
Object.prototype.hasOwnProperty). This way code stays both simple and safe.

------
breck
> Our program, however, is still susceptible to potential misbehaving. Let’s
> say our user decides to call his blog post "hasOwnProperty".

This will virtually never happen. If you're worried about it, put an early
return (if (name === 'hasOwnProperty') return). End of story.

~~~
mtts
Downvoted because "this will virtually never happen" is, uhm, for want of a
better term, _incredibly stupid_.

Allow what you want to allow and disallow everything else.

~~~
breck
Sorry, this will happen 00.0001% of the time. Orders of magnitude less than
dozens of other more pressing problems.

~~~
riffraff
downvoted for assuming that "chance of happening in random usage" has any
correlation to what a determinated user will actually try to do.

This is like saying "oh, but nobody will ever insert SQL in our forms!"

~~~
breck
good point.

------
dbbolton
Contrary to this Stack thread:
[http://stackoverflow.com/questions/1143498/difference-
betwee...](http://stackoverflow.com/questions/1143498/difference-between-an-
object-and-a-hash)

~~~
charliesome
I feel like you missed the point of the article.

~~~
dbbolton
I feel like you misjudged the tone of my comment.

------
hakaaak
Other than lists which can be represented by JSON arrays, hashes do fine jobs
at describing most data and most "objects".

This post goes off the rails almost immediately with "Normally this would work
fine, but let’s consider that the user could pick any of the keys that are
present in any JavaScript object as a name." WTF? The author almost
immediately is confusing his/her current situation with the request body and
what's in it.

Then we find ourselves quizzically with "We therefore change our code to
leverage hasOwnProperty". Umm, I don't think most developers would find
themselves "therefore" doing this.

~~~
alanh
I think you are missing something.

If I understood the article’s example, he’s suggesting the (perfectly
plausible) use case of a new blog post named, e.g., `hasOwnProperty`. Then
they would want to set a key named `hasOwnProperty` in the `posts` object, but
`hasOwnProperty` is already defined (via inheritance, since all objects have
it) as a function (well, native code)… so now you have either (a) prevented
your blog from allowing a reasonable blog post title, or (b) seriously mucked
up your blog by accepting a blog post title that has the side effect of
_redefining a basic object method._

The author is not confused.

