Skip to content

Change property modifier in mapped type based on condition #32562

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
pleerock opened this issue Jul 25, 2019 · 4 comments
Open

Change property modifier in mapped type based on condition #32562

pleerock opened this issue Jul 25, 2019 · 4 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Domain: Mapped Types The issue relates to mapped types Suggestion An idea for TypeScript

Comments

@pleerock
Copy link

As I know there is no way to change property modifier of mapped types based on conditional types. Here is my use case:

export class MakeItRequired<T extends ModelValue<any>> {
    target: "MakeItRequired" = "MakeItRequired"
    constructor(public option: T) {    
    }
}

export type ModelValue<T> = 
    T extends StringConstructor ? string : 
    T extends NumberConstructor ? number : 
    T extends BooleanConstructor ? boolean :
    unknown

export type ModelFromSchema<T> = {
    [P in keyof T]?: 
        T[P] extends MakeItRequired<infer U> ? ModelValue<U> :
        ModelValue<T[P]>
}

export const schema = {
    id: new MakeItRequired(Number),
    firstName: String,
    lastName: String
}

export const type: ModelFromSchema<typeof schema> = {}

My goal is to have id non optional, while having others optional.
Additional screenshoot from vscode:

Screenshot 2019-07-25 22 59 49

Can we have this feature?

@pleerock
Copy link
Author

pleerock commented Jul 25, 2019

Okay, I think I found the way it can be implemented right now:

export type ModelFromSchema<T> = {
    [P in keyof T]+?: 
        T[P] extends MakeItRequired<infer U> ? unknown :
        ModelValue<T[P]>
} & {
    [P in keyof T]-?: 
        T[P] extends MakeItRequired<infer U> ? ModelValue<U> :
        unknown
}

Looks like unknown saves us here.

Issue can be closed, but I would like team to pay attention on the scalability of the current approach.

EDIT 1: No, I was too happy about it. It doesn't work.

EDIT 2: Okay, found a resolution:

type MakeItRequiredKeyNames<T> = { [K in keyof T]: T[K] extends MakeItRequired<infer U> ? K : never }[keyof T];
type MakeItRequiredKeys<T> = Pick<T, MakeItRequiredKeyNames<T>>;

type NonMakeItRequiredKeyNames<T> = { [K in keyof T]: T[K] extends MakeItRequired<infer U> ? never : K }[keyof T];
type NonMakeItRequiredKeys<T> = Pick<T, NonMakeItRequiredKeyNames<T>>;


export type ModelFromSchema<T> = {
    [P in keyof NonMakeItRequiredKeys<T>]?: 
        T[P] extends MakeItRequired<infer U> ? never :
        ModelValue<T[P]>
} & {
    [P in keyof MakeItRequiredKeys<T>]: 
        T[P] extends MakeItRequired<infer U> ? ModelValue<U> :
        never
}

@AnyhowStep
Copy link
Contributor

AnyhowStep commented Jul 25, 2019

Just note that your types might look a little "ugly" in the tooltip. It'll still work just fine, though.

If you want to make it look a little better (maybe for debugging or something),

Identity<T> = T;
Merge<T> = (
  T extends any ?
  Identity<{ [k in keyof T] : T[k] }> :
  never
);

Then,

type Blah = Merge<ModelFromSchema<T>>;

When you hover over Blah, you should see the two objects merged into one


I'm on mobile and can't test it but I think the above works. I'll check again when I get home

@jcalz
Copy link
Contributor

jcalz commented Jul 25, 2019

Possibly relevant comment from another somewhat related issue (#31581, about detecting such modifiers)

I'd be interested in more streamlined manipulation of modifiers in mapped types. Detecting `readonly` and optional properties is indeed a bunch of hoop jumping, and then if you want to selectively *alter* the modifiers, you need to split the mapping into pieces and intersect them:
type SelectivePartial<T, K extends keyof T> =
  Partial<Pick<T, K>> & Required<Pick<T, Exclude<keyof T, K>>> extends
  infer U ? { [P in keyof U]: U[P] } : never;

type Foo = SelectivePartial<{ a: string, b: number, c?: boolean }, 'b'>
// type Foo = { b?: number | undefined; a: string; c: boolean; }

It would be a lot nicer to essentially read and selectively write modifiers inside the mapping directly. (Note the word "selectively"; the current support for altering modifiers is all-or-nothing ).

@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 Domain: Mapped Types The issue relates to mapped types labels Jul 31, 2019
@codinsonn
Copy link

Just note that your types might look a little "ugly" in the tooltip. It'll still work just fine, though.

If you want to make it look a little better (maybe for debugging or something),

Identity<T> = T;
Merge<T> = (
  T extends any ?
  Identity<{ [k in keyof T] : T[k] }> :
  never
);

Then,

type Blah = Merge<ModelFromSchema<T>>;

When you hover over Blah, you should see the two objects merged into one

I'm on mobile and can't test it but I think the above works. I'll check again when I get home

This works, but also seems to turn types like Date into { toString: () => string, ... }
Would there be a way to prevent this?

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 Domain: Mapped Types The issue relates to mapped types Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

5 participants