Proposal: syntactic sugar for extracting fields from objects

# Григорий Карелин (5 months ago)

Wouldn't it be nice to have syntax like this:

const obj = { {firstName, lastName from user.profile}, otherData: 'other data'  };

as a syntactic sugar for

const obj = {firstName: user.profile.firstName, lastName: user.profile.lastName, otherData: 'other data'};

Of cause at the moment we can write it in two steps:

const {fistName, lastName} = userProfile;
const obj = {firstName, lastName, otherData: 'other data'};

But why use extra variables?

Motivating example is lodash's .pick() method: lodash.com/docs/#pick

# guest271314 (5 months ago)

If not found in source firstName and/or lastName would be assigned the value undefined?

# Григорий Карелин (5 months ago)

Yep, in the same way as destructuring would work

вс, 26 мая 2019 г. в 17:52, guest271314 <guest271314 at gmail.com>:

# Michael Luder-Rosefield (5 months ago)

I tried to see if I could do this in a single destructuring step, but here is what happened:

var user = { profile: { firstName: 'Bob', lastName: 'Ruffward', x: 'hi' } }
var obj = { ...({firstName, lastName} = user.profile), otherData:
'otherData' }

So... what happened? (I'm sure you all know already)

obj ended up with all of user.profile's properties:

{ firstName: "Bob", lastName: "Ruffward", x: "hi", otherData: "otherData" }

and firstName and lastName were assigned as global variables.

firstName \\ "Bob"
lastName \\ "Ruffward"

Dammit babies, you've got to be kind.

# Bob Myers (5 months ago)

Similar proposals have been out there for years. For whatever reason, none have gotten traction.

Under one proposal, your scenario would be

const obj ={...user_profile.{firstName, lastName}, otherData: 'other data'};

rtm/js-pick-notation

Bob

---------- Forwarded message ----------

# guest271314 (5 months ago)

To create individual variables and an object on the same line you can set the default value of obj to previously destructured values within target

let {firstName, lastName, obj = {firstName, lastName, otherData: "other data"}} = user.profile;

The proposed syntax would eliminate duplicate code in the described context.

# guest271314 (5 months ago)

But why use extra variables?

Is the expected result for obj to be defined as a variable and firstName and lastName to not be defined as variables?

# Григорий Карелин (5 months ago)

Is the expected result for obj to be defined as a variable and firstName and lastName to not be defined as variables? Exactly

As you mentioned above, we may use default value while destructuring to achieve the same result, but it looks quite unobvious and creates extra variables

# guest271314 (5 months ago)

To use destructuring assignment and not create additional variables you can assign the value of target to a previously defined object

let obj = {otherData: "other data"};
({firstName:obj.firstName, lastName:obj.lastName} = user.profile);

Alternatively there are various approaches which can be used to get only specific properties from an abject and set those properties and values at a new object without using destructing assignment.

Using object rest and reduce()

let obj = {otherData: "other data", ...["firstName","lastName"].reduce((o, prop) => (o[prop] = user.profile[prop], o), {})};

Object.assign(), spread syntax, map() and computed property name

let obj = Object.assign({otherData: "other data"}, ...["firstName","lastName"]
            .map(prop => ({[prop]:user.profile[prop]})));

A function using Object.assign(), Object.entries(), filter(), in, map() which expects first parameter to be the source input object and second parameter to be an object containing new properties and values to be set and properties to be filtered from input object at first parameter where the values are initially set to undefined

const filterProps = (o, {...props} = {}) => Object.assign(props, ...Object.entries(o).filter(([key]) => key in props)
                      .map(([key, value]) => ({[key]:value})));
let obj = filterProps(user.profile, {otherData:"other data", firstName:void 0 /* , lastName: void 0 */});

or the original approach at the OP

const obj = {firstName: user.profile.firstName, lastName: user.profile.lastName, otherData: 'other data'};

which ultimately uses less code; where the proposal of using from which assigns the properties and values to the target object is not implemented.

# Bob Myers (5 months ago)
let obj = {otherData: "other data"};
({firstName:obj.firstName, lastName:obj.lastName} = user.profile);

I don't understand this.

Alternatively there are various approaches which can be used to get only specific properties from an abject and set those properties and values at a new object without using destructing assignment.

Using object rest and reduce()

"lastName"].reduce((o, prop) => (o[prop] = user.profile[prop], o), {})};```

`Object.assign()`, spread syntax and `map()`

```let obj = Object.assign({otherData: "other data"}, ...["firstName",
"lastName"].map(prop => ({[prop]:user.profile[prop]})));```

As the words "syntactic sugar" in the subject of the thread make clear, the OP is not merely looking for ways to assign one object's property into another--there are many ways to do that. He's looking for a terser, moire elegant way to do it, which hopefully would be moire semantic, less bug-prone, more type-checkable (for typed variants of the language), more reminiscent of existing syntax such as deconstruction, and potentially more optimizable by engines.

Bob

# guest271314 (5 months ago)
let obj = {otherData: "other data"};
({firstName:obj.firstName, lastName:obj.lastName} = user.profile);

I don't understand this.

developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Assigning_to_new_variable_names

He's looking for a terser

Not certain if destructuring assignment can currently be golfed more within the scope of the current JavaScript implementation, that is, without writing the property name twice, though might be possible.

Recollect a nodejs ability to write something similar to

var obj = {} = {};

within the scope of destructuring assignment, though not sure if that is relevant to this proposal.

The proposed destructuring assignment syntax example is 25 characters less than the non-destructing assignment example, which is terser.

One observation about the proposed syntax is that the values set at the target object would be set from the identifier on the left side of from which is similar to

var o = {x:1};
console.log(o.x = 2); // 2

though how will the property name be set at the object at target object instead of {"2":2}? How does the engine know when the expected result is {"x":2} and not {"2":2}? Certainly such functionality can be designed, for example, using the proposed key word from.

If more terse code is one of the features that would be achieved, why are the wrapping of {} around [prop,[...props]] from source necessary?

moire elegant way to do it,

"elegant" is subjective

which hopefully would be moire semantic, less bug-prone, more type-checkable (for typed variants of the language), more reminiscent of existing syntax such as deconstruction, and potentially more optimizable by engines.

What is bug prone about the code examples at OP?

The language "type-checkable" does not appear at the original proposal.

This proposal would resolve the issue of currently, in general, having to write the property name twice.

# Григорий Карелин (5 months ago)

Here are another examples, where "destructuring picking" I suggest whould be helpful.

==1A Now (redux related)

function mapStateToProps({user: {firstName, lastName}, common:
{currentPageURL: url}}) {
  return {firstName, lastName, url};
}

==1B Proposal

function mapStateToProps(state) {
  return {{firstName, lastName from state.user}, {currentPageURL as url
from state.common}};
}

Shorter!

==2A Now

async function saveNewUserName(name) {
  const {name} = await sendToServer({name});

  return {ok: true, payload: {name}}; // oh wait, which name is it again?
Argument or response?
}

== 2B Proposal

async function saveNewUserName(name) {
  const resp = await sendToServer({name});

  return {ok: true, {name from response}};
}

No accidental shadowing.

I know, I know, you can achieve all that without new syntax, via naming your variables properly and using long explicit expressions. But I think some sugar won't hurt. After all, remember destructuring? There's no reason to use it other than it's cool and short and expressive.

And as Bob has mentioned above I'm trying to reuse existing keywords.

The question about using curlies around picking. First, it's the way to tell interpreter/transpiler that it's picking operation, second, it's the way to tell where to pick from when doing several picking into one object from different destinations.

# Cyril Auburtin (5 months ago)

What I'd like is this syntax:

let o={a:1, b:2, c:3};
o.{a, b} // would be {a: 1, b: 2}
# Григорий Карелин (5 months ago)

With the syntax you propose what would be the way to to this:

const source = {foo: 1, bar: 2};
const result = {foo: source.foo, bar: source.bar, buzz: 3}

?

ср, 29 мая 2019 г. в 20:14, Cyril Auburtin <cyril.auburtin at gmail.com>:

# Bob Myers (5 months ago)

On Wed, May 29, 2019 at 10:28 AM Григорий Карелин <grundiss at gmail.com>

wrote:

Hi Cyril, With the syntax you propose what would be the way to to this:

const source = {foo: 1, bar: 2};
const result = {foo: source.foo, bar: source.bar, buzz: 3}

?

As already mentioned earlier in this thread:

const source = {foo: 1, bar: 2};
const result = {...source.{foo, bar}, buzz: 3};
# Григорий Карелин (5 months ago)

Well, I guess it might work too.

I personally like more verbose constructions, with keywords instead of dots, asterisks, etc :)

# Bob Myers (5 months ago)

This syntax merely combines and extends two existing notations:

  1. Dot notation to access properties, which has been in JS since it was initially designed, with the RHS extended to permit a set of properties in {} in addition to a plain old identifier as at present.
  2. Property spread notation ({...{}}).
# Григорий Карелин (5 months ago)

I agree.

So, what does community think? Do we want to have “destructuring picking” sugar in JS and if we do, which syntax looks more attractive?

I’d suggest to vote.

On Wed, 29 May 2019 at 21:55, Bob Myers <rtm at gol.com> wrote:

This syntax merely combines and extends two existing notations:

  1. Dot notation to access properties, which has been in JS since it was initially designed, with the RHS extended to permit a set of properties in {} in addition to a plain old identifier as at present.
  2. Property spread notation ({...{}}).

On Wed, May 29, 2019 at 11:27 AM Григорий Карелин <grundiss at gmail.com> wrote:

Well, I guess it might work too.

I personally like more verbose constructions, with keywords instead of dots, asterisks, etc :)

On Wed, 29 May 2019 at 21:03, Bob Myers <rtm at gol.com> wrote:

On Wed, May 29, 2019 at 10:28 AM Григорий Карелин <grundiss at gmail.com> wrote:

Hi Cyril, With the syntax you propose what would be the way to to this:

const source = {foo: 1, bar: 2};
const result = {foo: source.foo, bar: source.bar, buzz: 3}

?

As already mentioned earlier in this thread:

const source = {foo: 1, bar: 2};
const result = {...source.{foo, bar}, buzz: 3};

-- Отправлено с мобильного устройства

-- Отправлено с мобильного устройства

# Oriol _ (5 months ago)

If you want to add this you will need a champion, see tc39/ecma262/blob/master/CONTRIBUTING.md#new-feature-proposals

-- Oriol

El 29/5/19 a les 21:15, Григорий Карелин ha escrit: I agree.

So, what does community think? Do we want to have “destructuring picking” sugar in JS and if we do, which syntax looks more attractive?

I’d suggest to vote.

Отправлено с мобильного устройства

# Григорий Карелин (5 months ago)

True

I think it's possible to find someone who will represent the will of community.

At the moment the question is does community have will to add proposed sugar to the language, and if so, which version.

ср, 29 мая 2019 г. в 22:30, Oriol _ <oriol-bugzilla at hotmail.com>:

# guest271314 (5 months ago)

I think it's possible to find someone who will represent the will of community.

Individuals can compose the code right now.

At the moment the question is does community have will to add proposed sugar to the language, and if so, which version.

Why would there be any restriction on the versions of syntax which would achieve the requirement? The original proposal using from and other proposals could each be created, tested, specified.

# Григорий Карелин (5 months ago)

Wouldn't it be better to consolidate the decision? I mean as OP I vote for from, but if majority will say they better like x.{y, z} I'll take it.

чт, 30 мая 2019 г. в 06:35, guest271314 <guest271314 at gmail.com>:

# guest271314 (5 months ago)

Wouldn't it be better to consolidate the decision? I mean as OP I vote for from, but if majority will say they better like x.{y, z} I'll take it.

No. There should not be any prohibition as to the additions different solutions to a single proposal. Neither proposal is "better" right now as neither have been coded, tested, and if necessary, specified. A simple majority does not mean correct or complete. The more approaches available the more ability to compose the code from different perspectives, outputting the same result; expanding the language both in syntax and reach as to possible composition, without setting an arbitrary specification to a single majority at only this point in time.

Given in principle either proposal and approach would presumably output the same result, is the vote on the least code to output the result or user preference by way of vote? If there is a vote for which single proposal relevant to destructuring assignment, why should the criteria not be objectively the least code written to output same result once implemented - without user preference being involved at all? If x.{y, z} or ...x.{y, z} is least bytes then specify x.{y, z} based on least bytes alone.

The tersest have been able to achieve so far on a single line using an immediately invoked arrow function and object rest which requires writing the identifier twice

let obj = {otherData:'other data',...(({firstName,lastName})=>({firstName,lastName}))(user.profile)}

If part of the requirement for the proposal is terse code, following the pattern of an immediately invoked arrow function if = operator between expressions () the arrow > and return value could be omitted as being designated implicit immediately invoked arrow function with default return value set from the destructured parameters, or undefined set as value of target identifiers, or plain object {}, resulting in the syntax, within at least an object literal, possibly preceded by spread syntax, will result in

let obj = {otherData:'other data',...(({firstName,lastName})=(user.profile))}

or

let obj = {otherData:'other data',...{firstName,lastName}(user.profile)}

being equivalent to the object rest followed by immediately invoked arrow function.

# Григорий Карелин (5 months ago)

I'm new to this community, so I'd appreciate if you clarify: is that your opinion or is it kind of rule written somewhere?

чт, 30 мая 2019 г. в 09:59, guest271314 <guest271314 at gmail.com>:

# guest271314 (5 months ago)

Not a rule. Just an email to this board.

# Bob Myers (5 months ago)

I don't know what "community" means, other than a bunch of people subscribing to this ML, and I can't imagine how one would define, or achieve, or identify, a "consensus" of that community, or why or how the community would vote on anything, or what such the results of such a vote would mean.

The very next step is to identify a champion. Such a champion would presumably help to shape, review, and choose between alternatives for the proposals. However, given the failure of my half-hearted efforts to find a champion, and the fact that no one has emerged as champion over the several years since these discussions started, allow me to be pessimistic.

It's odd to me because features such as property spread/rest notation, and before that destructuring, have clearly demonstrated the appetite of the "community" for language changes to better support manipulation of properties--not surprising, since objects and their properties can be considered the fundamental data structures of the language. This specific proposal has a relatively small syntactic footprint in my opinion, and measures up well against the majority of criteria that people commonly apply to language design decisions and have been documented on this list. I can only conclude that wiser minds than my own have concluded that this particular feature simply does not rise to the level of priority of other features that are progressing down the pipeline.

WIth regard to the notion of implementing this feature on a test basis, the most obvious approach to doing that is as a Babel plug-in, but based on my research--please-correct me if I'm wrong--Babel supports many kind of transformations but not entirely new syntax as is the case here; that requires essentialy rewriting internal parts of its parser. I have experimented with a Sweet implementation with some success, but actually I'm not really sure what that is supposed to demonstrate or if anyone would care.

Bob

# Jacob Pratt (5 months ago)

My understanding is that Babel does support proposals, even if they require new syntax. Of course, it would require changes to the parser that's beyond my understanding of the codebase. I'd certainly help out in whatever ways I'm able to.

For the record, though, I actually had this idea completely separate from the proposal — I ran across it when searching to see if anyone else had proposed such a syntax/language feature.

# Bob Myers (5 months ago)

It would be fabulous if we could get one or more of these proposals implemented as a Babel feature, but I think this would require the Babel team making the relevant changes to Babylon, and my understanding is that they do this only for features that have at least already started down the committee track--understandable, but a kind of catch-22.

You are one of many people whoi have wondered about this kind of feature over the years, including dozens of Stack Overflow members, calling it by different names, including picking, extended destructuring, extended dot notation, picked properties, etc. My own proposal at rtm/js-pick-notation is just one of several, although I should mention that it represents the outcome of multiple iterations of research and thinking, and its documentation is IMHO relatively clean and complete, although it does not include the actual proposed changes to the ECMAScript spec which need to be created at some point.

Bob

# Augusto Moura (5 months ago)

Just bringing to the table the other side of the discussion (not agreeing with all of them)... IIRC the biggest problem with a pick syntax is the syntactic noise encouraging convoluted code. Even now just with destructuring and arrow functions code can get messy really quickly. Another argument is that a pick helper function is really easy to be implemented and the problem that the syntax resolves is mostly aesthetic (even that is questionable given the first argument, terseness != legibility).

IMHO just a pick function in the standard library would suffice most of the problems that the syntax is trying to solve. Maybe something like Object.pick or Object.pickKeys or Object.smooshIntoObjectTheValuesOf

Em qui, 30 de mai de 2019 às 15:43, Bob Myers <rtm at gol.com> escreveu:

# Bob Myers (5 months ago)

Another argument is that a pick helper function is really easy to be implemented and the problem that the syntax resolves is mostly aesthetic

We've been over this ground before.

Yes, there's a valid cost/benefit question for any syntactic sugar proposal, and no one would claim there is not one here as well.

However, we write o.p and {p: v}, where p is not required to be quoted, so being forced to quote each and every property name as the argument to a pick API is not very attractive. In addition, pick APIs such as lodash do not provide any of the machinery already designed and introduced for destructuring to allow renaming and defaults, which the extended dot notation gives us "for free", since the thing in curly brackets on the right-hand side is precisely a deconstructing construct, as in

obj.{a = 2, b: c, d: {e}}}

which defaults obj.a to the value 2, renames b to c on the way out, and plucks e from inside d; this kind of thing is hardly feasible in an API. This is not new syntax. We already have the syntax. The single new thing is allowing it on the right side of dots. That's it. It does not increase messiness, it reduces it.

Bob

# Cyril Auburtin (5 months ago)

After thinking about it, the dot-notation is maybe confusing, because

obj.{a}

looks like

obj.a

But the results are totally different

Maybe a different syntax might be clearer

obj{a, b, c}
// or
const pickedObj = {...obj{a, b, c}}
// or
const pickedObj = {...obj: {a, b, c}}
# guest271314 (5 months ago)
let obj = {a:1};
let pickedObj = {};
({a:pickedObj.a} = obj);

is already currently possible.

const pickedObj = {...obj: {a, b, c}} would be less code.

If c is not a property of obj would the expected result at pickedObj be {a:1, b:2, c:undefined} or {a:1, b:2} with c omitted?

# Bob Myers (5 months ago)

On Fri, May 31, 2019 at 2:08 AM Cyril Auburtin <cyril.auburtin at gmail.com> wrote:

After thinking about it, the dot-notation is maybe confusing, because

obj.{a}

looks like

obj.a

But the results are totally different

They are intended to look alike. The results are not "totally different"--in both cases, one or more properties in the object to the left of the dot are being referenced. That's what the dot means.

Bob

# guest271314 (5 months ago)

from implementation using JavaScript

const user = {profile:{firstName:"00", lastName:"11"}};
const from = (o, ...props) => Object.fromEntries(Object.entries(o).filter(([key]) => props.includes(key)));
let obj = {otherData:'other data',...from(user.profile, "firstName")};

which should be possible to code as

const user = {profile:{firstName:"00", lastName:"11"}};
 // preserve right-side/last value syntax to avoid confusion
let obj = {otherData:'other data',...{ from user.profile {firstName} }};

and, similar to developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Assigning_to_new_variable_names

const user = {profile:{firstName:"00", lastName:"11"}};
let obj = {otherData:'other data'};
({firstName /* , lastName, ...props */} from user.profile to obj);
# guest271314 (5 months ago)

==2A Now

async function saveNewUserName(name) {
  const {name} = await sendToServer({name});

  return {ok: true, payload: {name}}; // oh wait, which name is it again? Argument or response?
}

Note example 2A will throw an error.

# Isiah Meadows (4 months ago)

Nit: 2A will reject thanks to TDZ semantics with name in the RHS where it's defined in the LHS of the same statement. (Think of the function body's lexical context as being in a block where parameters are in a parent block context. The block variable shadows the parent variable for the whole block and just initially starts as unset.) So that error will get caught fairly quickly.

But aside from that:

I agree 1A is a bit too noisy, especially in the parameter side.

1B could be simplified a bit. Have you considered leveraging the existing object rest+spread and just standard property accesses? This is technically shorter than your suggestion and is much less cluttered. (I do see a frequent pattern of premature destructuring when it's shorter, easier, and simpler not to.)

return {...state.user.{firstName, lastName}, url: state.common.currentPageURL}

2A could be fixed to work like this, which is technically shorter than your example:

const resp = await sendToServer({name})
return {ok: true, payload: {name: resp.name}}

And likewise, you could simplify 2B to this:

const resp = await sendToServer({name})
return {ok: true, payload: resp.{name}}

Isiah Meadows contact at isiahmeadows.com, www.isiahmeadows.com

# guest271314 (4 months ago)

A slightly briefer version of from

const from = (i,o={},...p)=>p.map(k=>i[k]&&(o[k]=i[k]))&&o

Usage

let obj = from(user.profile,{otherData:"otherdata"},"firstName","lastName")

let obj = from(user.profile,{},"firstName")

and

let obj = {};
from(user.profile,obj,"firstName","lastName","not" /* not included if not property of input object */);
# Jacob Pratt (4 months ago)

Is there any interest from a TC39 member with to a possible pick notation or Object.pick method? It's been something proposed a few times over, typically using similar syntax, and seems to have broad support.

At the very least, couldn't this become a stage 0 proposal? Bob has already created a repository, and the process document specifies no entrance criteria for stage 0. If a champion were identified, stage 1 doesn't seem far off.

Jacob Pratt