118

Ok so I'm working away on a project in Nodes, and I've come across a small problem with the keys in object literals, I have the following set-up:

var required = {
    directories : {
        this.applicationPath                    : "Application " + this.application + " does not exists",
        this.applicationPath + "/configs"       : "Application config folder does not exists",
        this.applicationPath + "/controllers"   : "Application controllers folder does not exists",
        this.applicationPath + "/public"        : "Application public folder does not exists",
        this.applicationPath + "/views"         : "Application views folder does not exists"
    },
    files : {
        this.applicationPath + "/init.js"               : "Application init.js file does not exists",
        this.applicationPath + "/controllers/index.js"  : "Application index.js controller file does not exists",
        this.applicationPath + "/configs/application.js": "Application configs/application.js file does not exists",
        this.applicationPath + "/configs/server.js"     : "Application configs/server.js file does not exists"
    }
}

Ok so many of you will look at this and think it look's OK, but the compiler keeps telling me that I am missing a : (colon), which im not, it seems like the + or and the . are both effecting the compiler.

Now i believe (not sure), that object literals are created at compile time, and not run-time, meaning that dynamic variables such as this.applicationPath and concatenation are not going to be available :( :(

What's the best way to overcome an obstacle like this without having to rewrite large chunks of code.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
RobertPitt
  • 56,863
  • 21
  • 114
  • 161
  • You may find my somewhat related http://stackoverflow.com/questions/17841915/angularjs-ng-model-form-driven-by-ng-repeat-over-ui-model-description-data-how-t/17844354#17844354 of interest in this context. – vorburger Jul 24 '13 at 20:42
  • Possible dupe now? https://stackoverflow.com/a/19837961/1795429 – OneHoopyFrood Oct 11 '19 at 22:04

8 Answers8

218

Computed property names are supported in ECMAScript2015:

var name = 'key';
var value = 'value';
var o = {
  [name]: value
};
alert("o as json : " + JSON.stringify(o));

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer

jmny
  • 308
  • 2
  • 17
Rich Apodaca
  • 28,316
  • 16
  • 103
  • 129
  • 26
    With time this has become the most correct answer! – Gershom Maes Jan 11 '17 at 17:22
  • 3
    indeed! @RobertPitt could you change the accepted answer to this ? – orion elenzil Mar 21 '19 at 17:56
  • Wow. This is a new thing for me. Perfect. "Computed property names" , thaks for that concept. – yuceel Nov 07 '19 at 12:58
  • 1
    This is a more correct answer, in the sense that it answers the question that was asked better than the accepted answer: the accepted answer deals with bound objects, not object literals / "inline objects". – Daniel Brady Dec 16 '19 at 16:15
  • I remember seeing this years ago and thinking it was unnecessary. It's actually very useful with Typescript for since it infers the type at object creation implicitly and saves a lot of ugly, explicit boilerplate code. – Preom Apr 10 '21 at 22:02
111

Prior to ECMAScript 2015 (ed 6), an object literal (ECMAScript calls it an "object initializer") key must be one of:

  1. IdentifierName
  2. StringLiteral
  3. NumericLiteral

So you couldn't use an expression as the key in an initialiser. This was changed as of ECMAScript 2015 (see below). You could use an expression with square bracket notation to access a property, so to set the properties with an expression you had to do:

var required = { directories : {}};
required.directories[this.applicationPath] = "Application " + this.application + " does not exists";
required.directories[this.applicationPath + "/configs"] = "Application config folder does not exists";
...

and so on. Since this.applicationPath is reused a lot, better to store a reference to help with performance and cut down the amount of code:

var a = this.applicationPath;
var required = { directories : {}};
var rd = required.directories;
rd[a] = "Application " + this.application + " does not exists";
rd[a + "/configs"] = "Application config folder does not exists";
...

Edit

As of ECMAScript 2015 (ed 6), object initializers can have computed keys using:

[expression]: value

There is also shorthand syntax for property and method names.

See MDN: Object Initializer or ECMAScript Object Initializer.

RobG
  • 142,382
  • 31
  • 172
  • 209
46

You can set dynamic keys is with bracket notation:

required.directories[this.applicationPath + "/configs"] = "Application config folder does not exists";

(of course wherever you do this definition, this.applicationPath must exist)

But do you need this.applicationPath in the keys? How do you access theses values? Maybe you can just remove this.applicationPath from whatever value you use to access the properties.


But in case you need it:

You could use an array to initialize the keys if you want to avoid repeating a lot of code:

var dirs = ['configs', 'controllers', ...];
var files = ['init.js', 'controllers/index.js', ...];

var required = { directories: {}, files: {} };
required.directories[this.applicationPath] = "Application " + this.application + " does not exists";

for(var i = dirs.length; i--;) {
    required.directories[this.applicationPath + '/' + dirs[i]] = "Application " + dirs[i] + " folder does not exists";
}

for(var i = files.length; i--;) {
    // same here
}
Gabe Kopley
  • 16,281
  • 5
  • 47
  • 60
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • I *don't* need them in the key's, however it was just a preference, ill change it around, your approach seems most reasonable. – RobertPitt Jun 28 '11 at 01:34
  • Thanks Felix, I went for the "/config" approach as the key, and concatenated in the for loop, there was no reason for me to use the variables in the indexes but it's just late here, thanks again bud. – RobertPitt Jun 28 '11 at 01:46
  • 2
    I think this answer is in need of an update to mention [computed property names](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Computed_property_names). – JLRishe Nov 20 '17 at 08:23
  • Though verbose you can use: `{ ...Object.defineProperty({}, key, { value, enumerable: true }) }`, where key and value are variables spread into in a literal object notation by defining an object with that key and value. Though `{ [key]: value }` would be concise where supported. – bucabay Jul 01 '19 at 19:20
8

Inspired by how babel coverts the new ES6 syntax ({[expression]: value}) to old Javascript, I learned that you can do it with a one liner:

var obj = (_obj = {}, _obj[expression] = value, _obj);

Example:

var dynamic_key = "hello";
var value = "world";
var obj = (_obj = {}, _obj[dynamic_key] = value, _obj);

console.log(obj);
// Object {hello: "world"}

(Tested on latest Chrome)

jmny
  • 308
  • 2
  • 17
Dan Barzilay
  • 4,974
  • 5
  • 27
  • 39
3

If you have a deep object structure (such as Grunt config), it's sometimes convenient to be able to return dynamically-generated object keys using the bracket notation outlined by Felix, but inline within the object structure. This can be achieved by using a function to dynamically return an object within the context of the deep object; in the case for the code in this question, something like this:

var required = {
    directories : function() {
        var o = {};
        o[this.applicationPath] = "Application " + this.application + " does not exists";
        o[this.applicationPath + "/configs"] = "Application config folder does not exists";
        o[this.applicationPath + "/controllers"] = "Application controllers folder does not exists";
        o[this.applicationPath + "/public"] = "Application public folder does not exists";
        o[this.applicationPath + "/views"] = "Application views folder does not exists";
        return o;
    }(),
    files : function() {
        var o = {};
        o[this.applicationPath + "/init.js"] = "Application init.js file does not exists";
        o[this.applicationPath + "/controllers/index.js"]  = "Application index.js controller file does not exists";
        o[this.applicationPath + "/configs/application.js"] ="Application configs/application.js file does not exists";
        o[this.applicationPath + "/configs/server.js"]     ="Application configs/server.js file does not exists";
        return o;
    }()
}

This fiddle validates this approach.

Community
  • 1
  • 1
DaveAlden
  • 30,083
  • 11
  • 93
  • 155
3

An old question, and the answers were correct at the time, but times change. In case someone digs it up in a google search, new javascript versions (ES6) allow using expressions as keys for object literals, if they are surrounded in square brackets: var obj={["a"+Math.PI]:42}

2

For object literals, Javascript/ECMAScript script specifies keys be either a valid IdentifierName, a string literal, or a number credit RobG (even hex) . Not an expression, which is what required.applicationPath + "/configs" is.

Community
  • 1
  • 1
MooGoo
  • 46,796
  • 4
  • 39
  • 32
  • does `this.applicationPath` not count as a valid identifier ? – RobertPitt Jun 28 '11 at 01:26
  • Also an expression, as the value of `this` cannot be known until runtime. – MooGoo Jun 28 '11 at 01:31
  • RobertPitt - no, it is an expression. – RobG Jun 28 '11 at 03:14
  • note that the key can be an *IdentifierName*, which is different to being an identifier. If a key with the same name as an identifier is used (say a variable name), it creates a property with that name, it does not resolve the identifier and create a property with the value of the identifier (otherwise it would be being treated as an expression, which it can't). – RobG Jun 28 '11 at 03:25
0

the problem is from using 'this' because it doesn't refer to anything smart*. create the static literal with the applicationPath in it.

var required={
    "applicationPath":"someWhereOverTheRainboW"
};

Then use

required.directories={};
required.directories[required.applicationPath + "/configs"]="Application config folder does not exists";
....

to fill it dynamically

Edit; I rushed with my first idea, it didn't work. The above works now - sorry for that!

* the keyword 'this' is very smart :) but it often refers to the window object or the element, the event has been fired on or the called 'active' object. Thus, creating a lot of confusion ;)

japrescott
  • 4,736
  • 3
  • 25
  • 37