TC39 Proposal: Inline object/spread destructuring

Introduction

This proposal introduces a new syntax for destructuring objects in JavaScript using the from keyword or an alternative syntax below. This syntax aims to provide a more concise and readable way to extract multiple properties from an object, especially when these properties are to be used in the creation of a new object.

Motivation

Currently, in JavaScript, when one needs to pick specific properties from an object and use them to create another object, the process involves declaring a new variable using object destructuring and then using these variables to create the new object. This can be verbose, especially when dealing with multiple properties or when the property names are the same in both the source and target objects.

The proposed from syntax or the alternative syntax simplifies this process, making the code more concise and readable.

Proposed Syntax

Basic Syntax

function someFunction(data) {
  return {
    prop1 from data,
    prop2 from data,
    prop3 from data,
    // misc data...
  };
}

Shorthand Syntax

function someFunction(data) {
  return {
    ...({ prop1, prop2, prop3 } from data),
    // misc data...
  };
}

Alternative Syntax Using (:)

function someFunction(data) {
  return {
    { prop1, prop2, prop3 }: data, /* or inversed */
    // misc data...
  };
}

Semantics

  • The from keyword or alternative syntax can be used in an object literal to directly extract properties from another object.
  • When used, it creates a property in the new object with the same name as the property in the source object, and its value is the value of that property in the source object.
  • The shorthand syntax with ... allows for extracting multiple properties at once, similar to the existing object spread syntax but with the ability to pick specific properties.
  • The alternative syntax using : is provided as a more familiar form for developers who are used to the existing destructuring syntax.

Use Cases

This syntax is particularly useful in scenarios such as:

  • Picking specific properties from an object to create a new object.
  • Reducing boilerplate code when dealing with objects that have many properties.
  • Improving readability and maintainability of code that involves object property manipulation.

Backward Compatibility

This change is backward compatible as it introduces a new syntax without altering the existing functionality of JavaScript.

Polyfill/Shim

Due to the syntactic nature of this proposal, it cannot be polyfilled or shimmed in current JavaScript environments. It requires changes to the JavaScript parser.

Conclusion

The introduction of the from keyword for object destructuring in JavaScript offers a more concise and readable way to extract properties from objects. This proposal aims to enhance the language's expressiveness while maintaining backward compatibility and the spirit of JavaScript's existing syntax and design principles.

Alternatives considered

The Object.omit and Object.pick proposal would be useful for picking properties from objects as well, those the syntax isn't as conducive for plain object references, as the syntax uses plain strings for reference (e.g. ['a']) rather than being able to ref via property (e.g. { a }: data).

This would look like this:

function someFunction(data) {
  return {
    ...Object.pick(data, ['prop1', 'prop2', 'prop3']),
    // misc data...
  };
}

vs.

function someFunction(data) {
  return {
    { prop1, prop2, prop3 }: data, /* or inversed */
    // misc data...
  };
}

See also GitHub - rbuckton/proposal-shorthand-improvements: A proposal to introduce new shorthand assignment forms for ECMAScript object literals

1 Like

@rbuckton Does that proposal cover this sort of inline destructuring? If not, could we? :)

Read the proposal a bit closer - it does, and that's one of the main motivators for that proposal in general.

@claudiameadows I've looked over it a few times now and still do not see any examples that use inner-object (inline) destructuring. Everything appears to use assignments on the samne level as the destructuring, at least in the examples provided both in the README as well as the attached presentation slides.

Could you please help to clarify here? I appreciate it. Thanks!

Are you looking for GitHub - tc39/proposal-object-pick-or-omit: Ergonomic Dynamic Object Restructuring. instead?

@claudiameadows Specifically not Object.pick/omit

I am looking for syntax that would allow for inline destructuring without strings, so we can do something like what I showed in my examples above:

function someFunction(data) {
  return {
    { prop1, prop2, prop3 }: data, /* or inversed */
    // misc data...
  };
}

I actually ran into another IRL situation today where this would have been handy.

Here is the actual situation:

const dispatchData = {
  queries: ["SavedEntriesPage"],
  onCompletedData: {
    id: data.createSavedEntry.id,
    name: data.createSavedEntry.name,
    data: data.createSavedEntry.data,
    type: data.createSavedEntry.type
  }
};

This could be shortened to something like this, but this leaks those values out into the outer scope, and is often art risk of naming collisions as a result:

const data = /* some reactive value */;
const { id, name, data: innerData, type } = data;

const dispatchData = {
  queries: ["SavedEntriesPage"],
  onCompletedData: { id, name, innerData: data, type }
};

:white_check_mark: Instead, it would be useful to do something like this:

onCompletedData: { id, name, data, type } from data

:white_check_mark: or this:

onCompletedData: ({ id, name, data, type }: data)

:x: not this:

onCompletedData: Object.pick(data, ['id', 'name', 'data', 'type'])

Came across another use case that this could streamline.

Instead of this (which also pollutes the current scope with those variable names):

const { width, height } = img;
Object.assign(canvas, { width, height });

we could write this:

// using `from`
Object.assign(canvas, { width, height } from img);

// using `:`
Object.assign(canvas, { width, height }: img);

This is arguably simpler than even the traditional reference method, especially if you need to copy tons of properties:

Object.assign(canvas, { width: img.width, height: img.height });

Here, Object.pick would work as well:

Object.assign(canvas, Object.pick(img, ["width", "height"]));

But Object.pick does not provide the same affordances destructuring does, such aliasing inline like this:

// using `from`
Object.assign(canvas, { width: w, height: h } from img);

// using `:`
Object.assign(canvas, { width: w, height: h }: img);
1 Like

A solution to this is needed.

This is a current example that shows how there is only a clunky solution:

    const __select = (transaction) => {
      const { bookingDate, valueDate, transactionAmount, debtorName, creditorName } = transaction;
      return { bookingDate, valueDate, transactionAmount, ...(debtorName ? { debtorName }: { }), ...(creditorName ? { creditorName }: { }) };
    }
    ...
    .filter (transaction => __within (util_date_epoch_fromStr (transaction.bookingDate), date_beg, date_end))
    .map (transaction => __select (transaction)));

I'd like a more elegant way to specifically filter / select object items.

At least if I could something like the following ...

    const __select = (transaction) =>
      ({ bookingDate, valueDate, transactionAmount, ...(debtorName ? { debtorName }: { }), ...(creditorName ? { creditorName }: { }) } = transaction); 

or even better:

    const __select = (transaction) =>
      ({ bookingDate, valueDate, transactionAmount, ?debtorName, ?creditorName  } = transaction);

I don't really support any proposal that involves Object.* functions -- we should be moving away from these as they are clunky artifacts, rather we should be enhancing the existing syntax and preferably the existing concepts (e.g. following from ?. we can use ? to selectively include an item if it exists or ignore if it does not).

Perhaps expand the terminology to 'restructuring' or even better, move from 'spreading', 'destructuring', 'restructuring', to one word ... object 'partitioning' or something else which encompasses both directions. Just off the top of my head.

The way to rewrite this is:

const __select = ({ bookingDate, valueDate, transactionAmount, debtorName, creditorName }) => {
return { bookingDate, valueDate, transactionAmount, ...(debtorName && { debtorName }), ...creditorName && { creditorName }) };
}
...
.filter (transaction => __within (util_date_epoch_fromStr (transaction.bookingDate), date_beg, date_end))
.map (transaction => __select (transaction)));

Thanks, that does help of course, but still (1) this doesn't detract from my original point, being, that the syntax to achieve this "reconstruction" is clunky as it involves multiple duplicative symbols which are bad for maintainability, readability, etc and; (2) that I had to use a contorted construct and then you have proposed a slightly cleaner, but still contorted construct, helps amplify the point that the language needs a clean and straightforward way to "reconstruct" objects rather than people having to find and use a variety of unclean solutions.

Another situation today in which this sort of destructuring would've been super helpful:

Current implementation :man_shrugging:t2:

export const manager = {
  catalog,
  fetch: fetchType,
  autofetch: false,
  params,
  username: currentUser.username,
  role: currentUser.role,
  flags: currentUser.features,
};

Ideal implementation :white_check_mark:

export const manager = {
  catalog,
  fetch: fetchType,
  autofetch: false,
  params,
  { username, role, features: flags }: $currentUser,
};

It actually took me several rereadings to understand what exactly this code was intended to do, and I think the fact that it did take me a while - and me, not by any means a JS newbie - is a good reason to consider this particular syntax suspect.

I'd like to offer an alternative syntax suggestion that might be easier to parse at a glance:

export const manager = {
  catalog,
  fetch: fetchType,
  autofetch: false,
  params,
  ...currentUser.{username, role, features: flags},
};

the .{} operator would take the same syntax as object destructuring assignment, and it would be equivalent to performing the destructuring as usual, then taking all of the bound names (which must be unique, by definition) and assigning them as properties of a new, otherwise-empty object. It works in tandem with the spread operator as above, and it also works with optional chaining, or in the middle of a chain:

// Returns an object with 3 properties, or undefined
return settings?.{layout, width, height};
// Uses the object's own toString method on a subset of its own properties
console.log(`Partial dictionary: ${dict.{toString, foo, bar, baz}.toString()}`)

While the last example is a bit unusual (how many objects have methods that make sense to use with only a portion of their properties?) it could be made more useful by allowing the .{} operator to use an expansion of the destructuring syntax to include the method, accessor, and prototype syntax from object literals:

class Coord {
    x;
    y;
    get distance() {
        return Math.sqrt(x * x + y * y);
    }
}

const totalDistance = some.involved
                          .chain
                          .that
                          .produces
                          .a
                          .twoElement
                          .array
                          .{__proto__: Coord.prototype, 0: x, 1: y}
                          .distance;

or:

const greetingName = getUser().{firstName,
                                lastName,
                                preferredName,
                                get name() {
                                  return this.preferredName
                                      ?? `${this.firstName} ${this.LastName}`;
                                }}
                              .name;