Skip to content

Syntax for conditionally setting object propertiesΒ #45606

Closed
@jcomputer

Description

@jcomputer

Suggestion

πŸ” Search Terms

conditionally assigned properties conditional properties exactOptionalPropertyTypes

βœ… Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

Create a syntax for conditionally assigning a property in an object at creation time in-line. This is especially relevant now that exactOptionalPropertyTypes is launched. See below for a proposal of how to do this.

πŸ“ƒ Motivating Example

Imagine this piece of JavaScript code:

function myFunction(val) {
  const myObject = {
    name: 'foo',
  };
  if (val !== undefined) {
    myObject.value = val;
  }
  return myObject;
}

This is valid JS code, and a somewhat common pattern for setting an optional property on an object.

But how would we convert this into strict TypeScript code, particularly with immutable types? Perhaps something like:

interface MyType {
  readonly name: string;
  readonly value?: string;
}

function myFunction(val?: string) {
  const myObject: MyType = {
    name: 'foo',
  };
  if (val !== undefined) {
    myObject.value = val;   // Error: cannot assign to `value` because it is a readonly property.
  }
  return myObject;
}

Well that doesn't work. :-(

Another option before exactOptionalPropertyTypes was:

function myFunction(val?: string) {
  const myObject: MyType = {
    name: 'foo',
    value: val,  // Not great, setting an optional property to `undefined` instead of leaving it unset. And fails with exactOptionalPropertyTypes
  };
  return myObject;
}

But this is bad practice for the reasons outlined in exactOptionalPropertyTypes, and is an error if you use that flag now.

Using good practices combined with strict TypeScript settings is preventing us from accomplishing this somewhat common pattern for constructing immutable objects with some optional properties. One workaround is to use something like type Mutable<T> = {-readonly [key in keyof T]: T[key]} inside this function, but this is worsening type scritness by circumventing the original type's immutability. Or you could tell the compiler to assume the value is non-null (!), then conditionally delete that property after, but that's error-prone and is circumventing TypeScript's type checking. I'm not aware of any safer way to do this without fundamentally modifying the code (eg. creating a builder pattern).

Proposal

What if we had something like this:

interface MyType {
  readonly name: string;
  readonly value?: string;
}

function myFunction(val?: string) {
  const myObject: MyType = {
    name: 'foo';
    value?: val;  // If `val` is `undefined`, does not set this key. Otherwise, sets this key to the the value in `val`.
  };
  return myObject;
}

This code would compile to the JavaScript code written at the top of this FR. But importantly, unlike my other examples, this code will build successfully because TypeScript understands the optional (but not undefined) key will not be set on this object unless its value is defined. It is type-safe.

How isn't this just a new syntactic sugar for JavaScript?

This problematic code is unique to TypeScript. JavaScript has simple solutions that build and run just fine (see first code example), but TypeScript does not (without sacrificing strictness). As such, this problem falls in the realm of TypeScript to solve, as JavaScript has far less incentive to implement what for them would only be a very minor syntactic sugar rather than a fix for unsupported use-cases.

Additionally, this is not writing any code that a human wouldn't write themselves if asked to solve this problem in JavaScript. This would just be TypeScript equivalent syntax that transpiles to that form, in the same way TypeScript class methods transpile to MyClass.prototype.foo = function(... syntax in JavaScript despite no mention of "prototype" in the TypeScript code.

πŸ’» Use Cases

The primary use-case is as described above: immutable types being able to correctly set optional properties optionally without compromising strictness. In addition, this would also be a useful syntactic sugar for mutable use-cases, particularly when exactOptionalPropertyTypes is set, avoiding the verbosity of a bunch of if statements.

Alternative proposal

Here's another possible syntax that could solve the same problem, but with a bit more expressive power:

function myFunction(val?: string) {
  const myObject: MyType = {
    name: 'foo';
    // If the value of a key is `delete`, does not set that key. This would only be a TypeScript syntax,
    // it would compile either to the JS at the top of this FR or else to a similar solution bu using the
    // `delete` keyword on this key within the opposite if statement instead.
    value: val ?? delete;
  };
  return myObject;
}

This alternative syntax is a bit quirkier, but it has the advantage of not treating undefined as a special value. undefined seems to be the primary use-case here, especially after exactOptionalPropertyTypes, but some users may want to have more flexibility to customize this behavior (eg. for the corner case that a property is defined as foo?: string|undefined).

Metadata

Metadata

Assignees

No one assigned

    Labels

    DeclinedThe issue was declined as something which matches the TypeScript visionSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions