Typescript: Change property modifier in mapped type based on condition

Created on 25 Jul 2019  路  3Comments  路  Source: microsoft/TypeScript

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?

Awaiting More Feedback Mapped Types Suggestion

Most helpful comment

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](https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#improved-control-over-mapped-type-modifiers) for altering modifiers is all-or-nothing ).

All 3 comments

~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
}

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

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](https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#improved-control-over-mapped-type-modifiers) for altering modifiers is all-or-nothing ).
Was this page helpful?
0 / 5 - 0 ratings

Related issues

dlaberge picture dlaberge  路  3Comments

blendsdk picture blendsdk  路  3Comments

jbondc picture jbondc  路  3Comments

weswigham picture weswigham  路  3Comments

CyrusNajmabadi picture CyrusNajmabadi  路  3Comments