JAVASCRIPT / OBJECTS AND PROPERTY MECHANICS
Computed keys and dynamic property names
Create and read object properties whose names are computed at runtime, and predict the exact key a computed expression produces.
What you will learn
- Name a property from a variable or expression with { [expr]: value }
- Predict the resulting key: symbols stay symbols, everything else becomes a string
- Recognise silent overwrites when two computed keys stringify to the same text
- Build data-driven key sets with obj[key] = value or Object.fromEntries
Understanding Computed keys and dynamic property names
Square brackets around a key inside an object literal tell the engine to evaluate the expression and use the result as the property name. Without them the text is taken literally, so { field: 1 } always creates a property called field, even when field is a variable holding "email". The expression can be a variable, a template literal, a function call, or arithmetic, and it runs at the moment the literal is evaluated, not later.
The result then gets converted, because a property key in JavaScript can only be a string or a symbol; there is no third option. The value your expression produces goes through the same conversion that bracket access uses: a symbol is kept as it is, and anything else is turned into a string. That one rule explains why [2] and ["2"] name the same property, why an object used as a key shows up as "[object Object]", and why two different objects as keys quietly collapse into one.
An object literal spells out a fixed number of properties, one key expression each, and that is the boundary of the syntax. When the number of keys comes from data, assign in a loop with obj[key] = value, or map to [key, value] pairs and pass them to Object.fromEntries. When the keys are really arbitrary values whose identity matters rather than names, stringifying them destroys that identity, and a Map is the correct structure instead.
Computed names also work for method, getter, and setter definitions, so a whole API surface can be named from data at construction time.
const field = "email";
const suffix = "Verified";
const n = 2;
const record = {
[field]: "ada@example.com",
[field + suffix]: true,
[`slot${n}`]: "backup",
[n]: "numeric key"
};
console.log(JSON.stringify(record));
console.log(Object.keys(record).join(" | "));
console.log(record[field + suffix], typeof Object.keys(record)[0]);A computed key is an expression evaluated when the literal runs, and its result is converted to a string or kept as a symbol to name the property.
Worked examples
What the key expression converts to
Shows objects and arrays being stringified into keys, one collision, and a symbol key that stays a symbol.
const a = { id: 1 };
const b = { id: 2 };
const tag = Symbol("tag");
const bag = {
[a]: "first",
[b]: "second",
[["x", "y"]]: "from array",
[tag]: "hidden"
};
console.log(Object.keys(bag).join(" | "));
console.log(bag["[object Object]"]);
console.log(bag[tag], Object.keys(bag).includes("tag"));Example explained
Line 1[a] converts the object to a string, and a plain object's default string form is "[object Object]".
Line 2[b] converts to that same string, so "second" overwrites "first" with no error and no warning.
Line 3An array converts by joining with commas, so [["x", "y"]] creates the key "x,y".
Line 4[tag] stays a symbol, so the property is reachable through bag[tag] but Object.keys ignores it; the symbol's description "tag" is not a key.
Keys derived from data
Renames every key of an object, and contrasts entry pairs with computed keys in a literal.
const raw = { first_name: "Ada", last_name: "Lovelace", user_age: 36 };
const camel = s => s.replace(/_(\w)/g, (_, c) => c.toUpperCase());
const clean = Object.fromEntries(
Object.entries(raw).map(([k, v]) => [camel(k), v])
);
console.log(JSON.stringify(clean));
const flag = (name, value) => ({ [name]: value, [name + "Set"]: true });
console.log(JSON.stringify(flag(camel("last_name"), "Lovelace")));Example explained
Line 1Object.entries yields [key, value] pairs, and the map rewrites only the key half of each pair.
Line 2The brackets in [camel(k), v] build an array, not a computed key; only brackets sitting in the key position of a literal are computed keys.
Line 3Object.fromEntries is what you reach for when the number of keys comes from data, since a literal can only list a fixed set.
Line 4Inside flag, two computed keys are derived from one argument, and both are fixed at the moment that literal is evaluated.
When the key expression runs
Traces the evaluation order of key and value expressions and uses a computed name for a method.
const log = [];
const trace = label => { log.push(label); return label; };
let action = "save";
const obj = {
[trace("k1")]: trace("v1"),
[trace("k2")]: trace("v2"),
[action + "Draft"]() { return "drafted"; }
};
action = "delete";
console.log(log.join(" -> "));
console.log(Object.keys(obj).join(","));
console.log(obj.saveDraft());Example explained
Line 1Properties are processed top to bottom, and within each one the key expression runs before its value expression, which is why the log interleaves.
Line 2Each key expression runs exactly once; it is not a live link, so reassigning action afterwards cannot rename saveDraft.
Line 3[action + "Draft"]() { ... } is method shorthand with a computed name, and the same bracket form works for get and set definitions.
Line 4The finished property is an ordinary one, so obj.saveDraft() works with dot access even though the name was built dynamically.
Important notes
A computed key that converts to an integer index, such as [2], is ordered before string keys, so it appears first in Object.keys and JSON.stringify regardless of where you wrote it.
Symbol-keyed properties created this way are skipped by Object.keys, for...in, and JSON.stringify; use Object.getOwnPropertySymbols or Reflect.ownKeys to see them.
Common mistakes
Writing { key: value } when key is a variable holding the name: you get a property literally called "key", and obj[keyVariable] returns undefined.
Using an object or array as a computed key: it becomes "[object Object]" or a comma-joined string, so unrelated keys overwrite each other with no error.
Expecting the property to follow the variable: the key is fixed when the literal is evaluated, so changing field from "email" to "phone" later renames nothing.
Try it yourself
Change, predict, then run
Write renameKey(obj, from, to) that returns a new object with the same values but one key renamed, using Object.entries, a computed key, and Object.fromEntries. Call renameKey({ a: 1, b: 2 }, "b", "z") and confirm a still comes before z.
Open the JavaScript workspaceCheck your understanding
Given const a = { id: 1 }, b = { id: 2 }, s = Symbol("k"); and const obj = { [a]: 1, [b]: 2, [s]: 3, ["2"]: 4, [2]: 5 }; what is Object.keys(obj).length?
- 2
- 3
- 4
- 5
Show answer
Both a and b convert to the string "[object Object]", so they describe one property, and ["2"] and [2] also name the same key, leaving "2" and "[object Object]". 3 is tempting because the symbol-keyed property genuinely exists, but Object.keys never reports symbol keys; Reflect.ownKeys(obj) would return three.