# Bracket notation accepting arrays

**URL:** <https://es.discourse.group/t/bracket-notation-accepting-arrays/791>\
**Category:** 💡 Ideas\
**Created:** [May 14, 2021, 10:30pm UTC](https://es.discourse.group/t/bracket-notation-accepting-arrays/791 "2021-05-14T22:30:42Z")\
**Posts on this page:** 16\
**Page:** 1

<div class="post-metadata">

**Author:** ![robertpage](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/robertpage/32/823_2.png) [@robertpage](https://es.discourse.group/u/robertpage)\
**Post date:** [May 14, 2021, 10:30pm UTC](https://es.discourse.group/t/bracket-notation-accepting-arrays/791/1 "2021-05-14T22:30:42Z")

</div>

Deeply nested object data can get unreadable pretty quick. It also can be problematic if accessing data with dot or bracket notation in multiple places in your code and the shape of the data changes. Currently bracket notation accepts strings but there would be a lot of useful things you could do if it accepted arrays. Some simple examples might be:

```javascript
const obj = {
    name: {
        fName: "John",
        lName: "Smith"
    }
}
obj[["name", "fName"]] = "Jane";
const path = ["name", "fName"];
obj[path] = "Jane";
obj[path] = obj[path] + "y"; // value becomes Janey
obj["name.fName".split(".")] = "Jane";

```

In scenarios where you have nested JSON you could more easily dynamically construct the path for accessing your data. Also, it would allow you to store deeply nested paths in variables so that you didn’t repeat the path constantly and had a single source of truth. So instead of having obj.users[0].userInfo.name.firstName throughout your code you could have the following:

```javascript
const firstName = (index) => `users.${index}.userInfo.name.firstName`.split(".");

let name = obj[firstName(0)];
obj[firstName(0)] = "Jane";

```

Thanks for taking a look! Any opinions/feedback is appreciated!

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [May 14, 2021, 10:41pm UTC](https://es.discourse.group/t/bracket-notation-accepting-arrays/791/2 "2021-05-14T22:41:58Z")

</div>

All of those suggestions already do something - arrays get stringified when used as object keys.

---

<div class="post-metadata">

**Author:** ![theScottyJam](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/thescottyjam/32/616_2.png) [@theScottyJam](https://es.discourse.group/u/theScottyJam)\
**Post date:** [May 14, 2021, 10:44pm UTC](https://es.discourse.group/t/bracket-notation-accepting-arrays/791/3 "2021-05-14T22:44:56Z")

</div>

The main issue here is that `obj[['x', 'y']]` is already valid Javascript. Anything that goes in the brackets gets stringified before a lookup, so `obj[['x', 'y']]` would cause the key `'x,y'` to be accessed from `obj`.

e.g.

```javascript
const obj = { 'x,y': 42 }
console.log(obj[['x','y']]) // Logs 42

```

This is not a common scenario, but I'm sure people out there in the wild do this, and we don't want to break their code.

Maybe a helper function would be better?

```javascript
const obj = {
    name: {
        fName: "John",
        lName: "Smith"
    }
}

Object.getProperty(obj, 'name') // Returns { fName: 'John", lName: 'Smith' }
Object.getProperty(obj, 'name', 'fName') // 'Jane'
Object.getProperty(obj, ...['name', 'lName']) // 'Smith'

```

---

<div class="post-metadata">

**Author:** ![robertpage](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/robertpage/32/823_2.png) [@robertpage](https://es.discourse.group/u/robertpage)\
**Post date:** [May 14, 2021, 10:50pm UTC](https://es.discourse.group/t/bracket-notation-accepting-arrays/791/4 "2021-05-14T22:50:30Z")

</div>

Totally! Yeah, I should have thought of that 😅. You currently just get a string key of that array. I like the idea of a helper function. How would you set values using the array?

---

<div class="post-metadata">

**Author:** ![theScottyJam](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/thescottyjam/32/616_2.png) [@theScottyJam](https://es.discourse.group/u/theScottyJam)\
**Post date:** [May 14, 2021, 10:59pm UTC](https://es.discourse.group/t/bracket-notation-accepting-arrays/791/5 "2021-05-14T22:59:26Z")

</div>

Didn't think about that one. I guess you would need a setProperty function too:

```javascript
Object.setProperty(obj, 'value', 'key1', 'key2') // This one is pretty ugly
// or
Object.setProperty(obj, 'value', ['key1', 'key2'])

```

---

<div class="post-metadata">

**Author:** ![robertpage](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/robertpage/32/823_2.png) [@robertpage](https://es.discourse.group/u/robertpage)\
**Post date:** [May 14, 2021, 11:28pm UTC](https://es.discourse.group/t/bracket-notation-accepting-arrays/791/6 "2021-05-14T23:28:57Z")

</div>

That all makes sense! A get and set property function seems like a solid approach. I do wonder though if there would be some option that mimics bracket notation just for simplicity's sake.

Honestly, as we go back and forth I'm starting to wonder if there is a way to achieve something similar with proxy objects? 🤔

---

<div class="post-metadata">

**Author:** ![theScottyJam](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/thescottyjam/32/616_2.png) [@theScottyJam](https://es.discourse.group/u/theScottyJam)\
**Post date:** [May 15, 2021, 12:06am UTC](https://es.discourse.group/t/bracket-notation-accepting-arrays/791/7 "2021-05-15T00:06:04Z")

</div>

Even with proxies, a key will be stringified before it's passed to the handler function - which is ok if none of your keys have commas, and you want to just `.split(',')` the key on commas.

I will also note that utility libraries such as lodash will provide these helper functions for you. Lodash provides them as [\_.get()](https://lodash.com/docs/4.17.15#get) and [\_.set()](https://lodash.com/docs/4.17.15#set). For example:

```javascript
const obj = {
    name: {
        fName: "John",
        lName: "Smith"
    }
}

console.log(_.get(obj, ['name', 'fName'])) // John
_.set(obj, ['name', 'lName'], 'newLastName')

```

---

<div class="post-metadata">

**Author:** ![simonkcleung](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/simonkcleung/32/785_2.png) [@simonkcleung](https://es.discourse.group/u/simonkcleung)\
**Post date:** [May 15, 2021, 1:58pm UTC](https://es.discourse.group/t/bracket-notation-accepting-arrays/791/8 "2021-05-15T13:58:39Z")

</div>

You may use Proxy like this:

```javascript
const obj = {
	name: {
        fName: "John",
        lName: "Smith"
	}
}

const p = new Proxy(obj, {
		get: function(target, prop, receiver){
			prop = this._obj[prop];
			if (typeof prop === "object") {
				this._obj = prop;
				return receiver;
			};
			this._obj = target;
			return prop;
		},
		set: function(target, prop, value, receiver){
			this._obj[prop] = value;
			this._obj = target;
			return value;
		},		
		_obj: obj
	}
);

console.log(p.name.fName);
console.log(p.name.lName);
p.name.address = { city: "NY" };
console.log(p.name.address.city);

```

But be careful that if you call any method, the "this" object may be the global object.

---

<div class="post-metadata">

**Author:** ![theScottyJam](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/thescottyjam/32/616_2.png) [@theScottyJam](https://es.discourse.group/u/theScottyJam)\
**Post date:** [May 15, 2021, 2:42pm UTC](https://es.discourse.group/t/bracket-notation-accepting-arrays/791/9 "2021-05-15T14:42:14Z")

</div>

Maybe I'm missing something, but how does that proxy implementation help someone to get a nested property from a list of keys?

---

<div class="post-metadata">

**Author:** ![simonkcleung](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/simonkcleung/32/785_2.png) [@simonkcleung](https://es.discourse.group/u/simonkcleung)\
**Post date:** [May 15, 2021, 4:11pm UTC](https://es.discourse.group/t/bracket-notation-accepting-arrays/791/10 "2021-05-15T16:11:19Z")

</div>

This -\>

```javascript
const obj = {
	name: {
        fName: "John",
        lName: "Smith"
	}
}

const p = new Proxy(obj, {
		get: function(target, prop, receiver){
			prop = prop.split(",");
			let n = prop.length -1;
			for (let i = 0; i < n; i++){
				target = target[prop[i]];
			};
			return target[prop[n]];
		},
		set: function(target, prop, value, receiver){
			prop = prop.split(",");
			let n = prop.length -1;
			for (let i = 0; i < n; i++){
				target = target[prop[i]];
			};
			return target[prop[n]] = value;
		} 
	}
);
console.log(p[["name","fName"]]);
console.log(p[["name","lName"]]);
p[["name","address"]] = { city: "NY" };
console.log(p["name,address,city"]);

```

---

<div class="post-metadata">

**Author:** ![Clemdz](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/clemdz/32/813_2.png) [@Clemdz](https://es.discourse.group/u/Clemdz)\
**Post date:** [May 15, 2021, 5:41pm UTC](https://es.discourse.group/t/bracket-notation-accepting-arrays/791/11 "2021-05-15T17:41:23Z")

</div>

I don't know why, but this made me think about your [Overloadable data accessor operator proposal](https://es.discourse.group/t/overloadable-data-accesor-operator/662/8) @theScottyJam.

With the only difference that the `@[]` operator could not only be used to enable overloading access on the object, but also to accept any object type as a key (in contrary to the normal `[]` accessor that directly calls `.toString` on the object it receives, so that it is always a string).

The advantage would be that any given object would stay intact inside `@[]` custom `get` / `set` handlers, whereas in the proxy solution, objects like `({a: 1, b:2})` would have been already stringified to `"[object Object]"`, and so being unusable inside the proxy `get` / `set` handlers.

Also, we would explicitly indicate that we want to treat the object as is (and not use its stringified value), so it would be more understandable than using proxies under the hood.

Here is an example to show how it could work for this use case:

```javascript
// Custom getter function
const getter = function (path) {
  let value = this;
  path.forEach(key => { value = value?.[key]; });
  return value;
}

// Custom setter function
const setter = function (path, value) {
  let target = this;
  let lastKey = path.at(-1);
  path.slice(0, -1).forEach(key => {
    if (typeof target[key] !== "object") target[key] = {};
    target = target[key];
  });
  target[lastKey] = value;
}

// The object, with overloaded data accessor operator
const obj = {
  // Data accessor overloads
  [Symbol.getDataValue]: getter,
  [Symbol.setDataValue]: setter,

  // Data
  name: {
    fName: "John",
    lName: "Smith",
  },

  // Just to test
  "name,fName": "test",
}

// We can now use the `@[]` operator to get / set values using an array
const path = ["name", "fName"];
obj@[path] = "Jane";
obj@[path] += "y"; // value becomes "Janey"

// Normal `[]` accessor still works as usual
obj[path] === "test"

```

---

<div class="post-metadata">

**Author:** ![theScottyJam](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/thescottyjam/32/616_2.png) [@theScottyJam](https://es.discourse.group/u/theScottyJam)\
**Post date:** [May 15, 2021, 6:30pm UTC](https://es.discourse.group/t/bracket-notation-accepting-arrays/791/12 "2021-05-15T18:30:01Z")

</div>

That's a nice connection that I didn't even think about @Clemdz - that would be another good solution if we wanted to enable this functionality on specific objects without being too tricky. It's interesting the different ideas that come out of that overloadable data accessor proposal.

---

<div class="post-metadata">

**Author:** ![senocular](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/senocular/32/642_2.png) [@senocular](https://es.discourse.group/u/senocular)\
**Post date:** [May 15, 2021, 10:52pm UTC](https://es.discourse.group/t/bracket-notation-accepting-arrays/791/13 "2021-05-15T22:52:36Z")

</div>

What about spreading in bracket notation? I don't think this isn't legal anywhere now

```javascript
const path = ["name", "fName"];
obj[...path] = "Jane";

```

though maybe like optional chaining assignment wouldn't be allowed? (Not too familiar with the restrictions around that.)

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [May 16, 2021, 8:01am UTC](https://es.discourse.group/t/bracket-notation-accepting-arrays/791/14 "2021-05-16T08:01:36Z")

</div>

You may be interested in this issue I created for using spread notation like that for Records

> <https://github.com/tc39/proposal-deep-path-properties-for-record/issues/17>
>
> There are at least two situations where needing to specify the path literally ca…n be frustrating:
> 
> \*\*Situation 1: Mutating deep path based on current value\*\*
> 
> \`\`\`js
> player = #{
> ...player,
> // 'stats.medals.gold' repeated
> stats.medals.gold: player.stats.medals.gold + 1
> }
> \`\`\`
> 
> \*\*Situation 2: writing utility function for updating a path\*\*
> 
> \`\`\`js
> function increment(record, ...path) {
> // even a 'simple' implementation is ~10 lines before adding error checks
> assert(path.length \> 0);
> let \[prop, ...rest\] = path;
> if (rest.length === 0) {
> return {
> ...record,
> \[prop\]: record\[prop\] + 1
> }
> }
> return {
> ...record,
> \[prop\]: increment(record\[prop\], ...rest)
> }
> }
> \`\`\`
> 
> \*\*Idea: Add syntax for dynamic paths\*\*
> 
> If a value could be used as a path then it can be reused and passed around. An array of string/number keys could be used for such a value. Above examples become:
> 
> \`\`\`js
> let path = \['stats', 'medals', 'gold'\];
> player = #{
> ...player,
> \[...path\]: player\[...path\] + 1
> }
> \`\`\`
> 
> \`\`\`js
> function increment(record, ...path) {
> assert(path.length \> 0);
> return #{ ...record, \[...path\]: record\[...path\] + 1 }
> }
> \`\`\`
> 
> \*\*Alternative: Add static helper\*\*
> 
> Alternatively this type of update could be added to the global \`Record\` object. Something like \`Record.update(record, path, update)\`.
> 
> \`\`\`js
> player = Record.update(player, \['stats', 'medals', 'gold'\], val =\> val + 1);
> \`\`\`
> 
> \`\`\`js
> function increment(record, ...path) {
> assert(path.length \> 0);
> return Record.update(record, path, val =\> val + 1);
> }
> \`\`\`

---

<div class="post-metadata">

**Author:** ![theScottyJam](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/thescottyjam/32/616_2.png) [@theScottyJam](https://es.discourse.group/u/theScottyJam)\
**Post date:** [May 16, 2021, 1:19pm UTC](https://es.discourse.group/t/bracket-notation-accepting-arrays/791/15 "2021-05-16T13:19:33Z")

</div>

The spread makes It a very intuitive syntax, and if it goes into records, then it certainly should go into normal objects too.

---

<div class="post-metadata">

**Author:** ![robertpage](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/robertpage/32/823_2.png) [@robertpage](https://es.discourse.group/u/robertpage)\
**Post date:** [May 16, 2021, 7:31pm UTC](https://es.discourse.group/t/bracket-notation-accepting-arrays/791/16 "2021-05-16T19:31:38Z")

</div>

Wow, wasn't getting notifications and didn't expect to see so many responses. I am aware of lodash's get and set options. I also really agree that the spread syntax is really intuitive. So many interesting and good points here!
