Skip to content

optional and readonly Filter Modifier for keyof #35103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
5 tasks done
1nVitr0 opened this issue Nov 14, 2019 · 6 comments
Open
5 tasks done

optional and readonly Filter Modifier for keyof #35103

1nVitr0 opened this issue Nov 14, 2019 · 6 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@1nVitr0
Copy link

1nVitr0 commented Nov 14, 2019

Search Terms

  • keyof modifiers
  • only optional keys
  • only readonly keys
  • pick optional properties
  • pick required properties
  • pick readonly properties

Suggestion

Adding Support for modifiers to keyof could be very helpful. I'm picturing something like this

type Koptional = keyof? T;
type Kreadonly = readonly keyof T;

// Negations
type Krequired = keyof!? T;
type Keditable = !readonly keyof T;

// Combinations
type KrequiredReadonly = readonly keyof!? T;

The modifiers would act as "filters", only selecting the keys fitting the given modifiers.


As @jcalz pointed out, it might make more sense to use the existing + and - operators instead of the !.

Use Cases

This could be really useful for selecting properties of types based on them being optional / readonly. A possible use case for this would be the React defaultProps object (see example).

Of course this could negatively affect readability as especially Mapped Types can get fairly long

type PickEditableRequired<T> = {
    [P in !readonly keyof!? T]: T[P];
};

but I think it's still in the realms of comprehensibility.

Examples

type PickOptional<T> = {
    [P in keyof? T]-?: T[P];
};

type DrinkProps = {
    whoGetsUpAndMakesIt: string,
    type?: "water" | "coffee" | "coke" | "more coffee",
    vessel?: "cup" | "glass" | "HUGE cup",
    amount?: number
};

class Drink extends React.Component<Required<DrinkProps>> {
    public static defaultProps: PickOptional<DrinkProps> = {
        type: "water",
        vessel: "glass",
        amount: 250
    }

    render() {
        return <p>
            {this.props.whoGetsUpAndMakesIt} gets up and pours
            {this.props.amount}ml of {this.props.type} into a
            {this.props.vessel}.
        </p>
    }
}

Especially with many optional props the current method of

public static defaultProps: Pick<DrinkProps, "type" | "vessel" | "amount"> = { ... };

can get fairly tedious, especially as you have to add new optional props at 3 places (the type definition, the Pick UtilityType and in the defaultProps). The method using PickOptional and the ?-keyof-modifier reduces it to 2, and immediately notifies you if you declared a parameter optional, but haven't defined it in defaultProps.

This would also work without React in terms of default values:

type Order = {
    item: string,
    amount?: number,
    shipping?: "standard" | "express"
}
const defaultOrder: PickOptional<Order> = {
    amount: 1,
    shipping: "standard"
}

function placeOrder(order: Order): void {
    let data: Required<Order> = {...defaultOrder, ...order};
    console.log(`You ordered ${data.amount} ${data.item} with ${data.shipping} shipping.`);
}

I'm sure there are more (and better) use cases for this feature I can't think of right now...

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, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.
@1nVitr0 1nVitr0 changed the title optional and readonly Modifier for keyof optional and readonly Filter Modifier for keyof Nov 14, 2019
@jcalz
Copy link
Contributor

jcalz commented Nov 14, 2019

Related to #31581

Relevant SO question.

Bikeshedding: I think ! in the type system is generally used to mean "definitely defined" and not "not". Instead you'd probably want to reuse the + and - modifier modifiers as introduced in TS2.8.

@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Nov 14, 2019
@1nVitr0
Copy link
Author

1nVitr0 commented Nov 14, 2019

Relevant SO question.

Thanks @jcalz , somehow I missed that So topic when I googled about this. The '+' / '-' makes sense, in my mind they were more like "add that modifier" and "remove that modifier" than "does it have that modifier?". Will add that to my question.

@charles-allen
Copy link

charles-allen commented May 16, 2020

[1] Here's a relevant SO answer for Optional/Non-Optional filtering.

[2] I think there's one more combination:

type KrequiredEditable = !readonly keyof!? T;

Edit: Silly me; of course 2x2 => 4 combinations:

type KOptionalWritable = !readonly keyof? T;
type KRequiredWritable = !readonly keyof!? T;
type KOptionalReadonly = readonly keyof? T;
type KRequiredReadonly = readonly keyof!? T;

[3] With respect to +/-, they do mean add/remove. I think it may be confused with this existing usage (which means "make writable" not "is writable?"):

export type Writeable<T> = { -readonly [P in keyof T]: T[P] };

@charles-allen
Copy link

An alternative syntax proposal

Since ! is already used as the non-null assertion operator (and grammatically it feels opposite to ?), I think using it here provides a clean syntax to filter for non-null/non-undefined (required) keys:

type KOptional = keyof? T; // only optional keys
type KRequired = keyof! T; // only required keys

A more explicit opposite of readonly is "writable":

type KReadonly = readonly keyof T; // only readonly keys
type KWritable = writable keyof T; // only writable keys

I find this much more readable, especially when you consider combinations:

type KOptionalWritable = writable keyof? T;
type KRequiredWritable = writable keyof! T;
type KOptionalReadonly = readonly keyof? T;
type KRequiredReadonly = readonly keyof! T;

@joshxyzhimself
Copy link

Looking for this,

@anthonyborell
Copy link

This would be really useful

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

6 participants