Proposal: syntactic sugar for extracting fields from objects
If not found in source firstName
and/or lastName
would be
assigned the value undefined
?
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.
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'};
Bob
---------- Forwarded message ----------
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.
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?
Is the expected result for
obj
to be defined as a variable andfirstName
andlastName
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
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.
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
let obj = {otherData: "other data"}; ({firstName:obj.firstName, lastName:obj.lastName} = user.profile);
I don't understand this.
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.
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.
What I'd like is this syntax:
let o={a:1, b:2, c:3};
o.{a, b} // would be {a: 1, b: 2}
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};
Well, I guess it might work too.
I personally like more verbose constructions, with keywords instead of dots, asterisks, etc :)
This syntax merely combines and extends two existing notations:
- 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. - Property spread notation (
{...{}}
).
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:
- 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.- 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};
-- Отправлено с мобильного устройства
-- Отправлено с мобильного устройства
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.
Отправлено с мобильного устройства
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>:
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.
Wouldn't it be better to consolidate the decision? I mean as OP I vote for
from
, but if majority will say they better likex.{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.
Not a rule. Just an email to this board.
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
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.
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
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:
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
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}}
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?
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
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);
==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.
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
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 */);
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
Wouldn't it be nice to have syntax like this:
as a syntactic sugar for
Of cause at the moment we can write it in two steps:
But why use extra variables?
Motivating example is lodash's .pick() method: lodash.com/docs/#pick