Skip to content
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

Type to invert optionality of properties #16173

Closed
CzBuCHi opened this issue May 31, 2017 · 10 comments
Closed

Type to invert optionality of properties #16173

CzBuCHi opened this issue May 31, 2017 · 10 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

@CzBuCHi
Copy link

CzBuCHi commented May 31, 2017

add new mapped type: Invert<T> ... I do not know, how to explain it in english, so i use code:

// proposal syntax
type Invert<T> = {
    [P in keyof T]?: T[P];
    [P? in keyof T]: T[P];
};

// usage
type Original = {
    first: any;
    second?: any;
}

type Inverted = Invert<Original>;

// type Inverted is threated as if declared like this:
type Inverted = {
    first?: any; // first is optional becasue it was required in Original
    second: any; // second is required becasue it was optional in Original
}

example usage: react defaultProps - to ensure, that all optional props are declared.

type SomeProps = {
    name: string;
    point?: { x: number, y: number };
}

class Some extends React.Component<SomeProps, void> {
    // proposal usage
    static defaultProps: Invert<SomeProps> = {
        // here i get compiler error if do not specify property 'point'
    };

    // current usage (worst)
    static defaultProps: any = {
        // here i dont get any errors at all
    };

    // better
    static defaultProps: Partial<SomeProps> = {
        // here i dont get any errors if i dont specify point
        // but at least get compiler error for typo...
    };

    // even better
    static defaultProps: { name?: string; point: { x: number, y: number } } = {
        // here i get compiler error if do not specify property 'point' and for typo
        // but i was forced to create new type ...
    };
}

priority: really low, almost negative :) - but if it can be done and isnt too hard ...

@saschanaz
Copy link
Contributor

Interesting use case for me, but how about this way:

type Optional<T> = {
    [key in keyof T]?: T[key];
}

interface DefaultProps {
    point: { x: number, y: number };
}
interface SomeProps extends Optional<DefaultProps> {
    name: string;
}

var x: SomeProps = {
    name: 'abc' // error if missed
};
var y: DefaultProps = {
    point: { x: 0, y: 0 } // error if missed
};

@CzBuCHi
Copy link
Author

CzBuCHi commented May 31, 2017

Yes that will work too ... but in the end u need to define two separated tytes...

@DanielRosenwasser DanielRosenwasser changed the title SUGGESTION: Inverted type Type to invert optionality of properties May 31, 2017
@mhegazy
Copy link
Contributor

mhegazy commented Aug 25, 2017

Duplicate of #13224 and #15012

@mhegazy mhegazy marked this as a duplicate of #13224 Aug 25, 2017
@mhegazy mhegazy added the Duplicate An existing issue was already created label Aug 25, 2017
@CzBuCHi
Copy link
Author

CzBuCHi commented Aug 26, 2017

@mhegazy i dont think this is a dupe,,.

both #13224 and #15012 are trying to make all properties required:

type Original = {
    first: any;
    second?: any;
}

type Result = {
    first: any;
    second: any;
}

im trying to 'flip' required flag on each property:

type Original = {
    first: any;
    second?: any;
}

type Result = {
    first?: any;        // because Original.first was required
    second: any;    // because Original.second was not required
}

but if u really think this is a dupe fell free to close it ...

@mhegazy
Copy link
Contributor

mhegazy commented Aug 28, 2017

do you have a scenario where flipping optionality is required?

@CzBuCHi
Copy link
Author

CzBuCHi commented Aug 29, 2017

@mhegazy look at my initial message ... bacically to have react component defaults required for optional props.

BTW: I think that i can achieve it by this code: (not tested)

module ISomeProps {
    export interface Req {
        first: any;
    }
    export interface Def {
        second:any;
    }
}
interface ISomeProps extends ISomeProps.Req, Partial<ISomeProps.Def> {}

class Some extends React.Component<ISomeProps, never> {
    static defaultProps: Partial<ISomeProps.Req> & ISomeProps.Def = {
        second: 42
    };
    
    ...
}

@Gerrit0
Copy link
Contributor

Gerrit0 commented Feb 22, 2020

You can do this with existing TS features.

We first need two helpers.

type OptionalKeys<T> = {
    [K in keyof T]-?: undefined extends { [K2 in keyof T]: K2 }[K] ? K : never
}[keyof T]
type RequiredKeys<T> = Exclude<keyof T, OptionalKeys<T>>

The RequiredKeys helper is straightforward, but OptionalKeys is kind of tricky. Optionality is preserved if you don't specify -? or +? when using a mapped type. This lets us detect if a property is marked with ? (as opposed to just being unioned with undefined).

That is, given:

type Original = {
    first: string;
    second?: number;
    req: any;
    opt?: any;
    unioned: string | undefined;
}

Then OptionalKeys<Original> = 'second' | 'opt', which does not include 'unioned', even though it may be undefined

With these helpers, then Invert just becomes a simple intersection type.

type Invert<T> = {
    [K in OptionalKeys<T>]-?: NonNullable<T[K]>
} & {
    [K in RequiredKeys<T>]+?: T[K]
}

This almost gives you the exact behavior that the OP asks for, but TS will display the properties as an intersection. You can merge the intersection back into a single object type with a mapped type that does nothing..

type MergeIntersection<T> = { [K in keyof T]: T[K] }

type OptionalKeys<T> = {
    [K in keyof T]-?: undefined extends { [K2 in keyof T]: K2 }[K] ? K : never
}[keyof T]
type RequiredKeys<T> = Exclude<keyof T, OptionalKeys<T>>

type Invert<T> = MergeIntersection<{
    [K in OptionalKeys<T>]-?: NonNullable<T[K]>
} & {
    [K in RequiredKeys<T>]+?: T[K]
}>

type Original = {
    first: string;
    second?: number;
    req: any;
    opt?: any;
    unioned: string | undefined;
}

type InvertedTest = Invert<Original>;
// On hover shows:
// type InvertedTest = {
//     second: number;
//     opt: any;
//     first?: string | undefined;
//     req?: any;
//     unioned?: string | undefined;
// }

Playground

@RyanCavanaugh
Copy link
Member

We're not adding new utility types to the lib.

@RyanCavanaugh RyanCavanaugh closed this as not planned Won't fix, can't repro, duplicate, stale Mar 4, 2024
@jkomyno
Copy link

jkomyno commented Mar 7, 2024

We're not adding new utility types to the lib.

Why? The Invert type defined above is quite useful when it comes to e.g. providing default values to an object containing some partials.

@RyanCavanaugh
Copy link
Member

People don't agree with the "right" definition of any given utility type and get upset that we didn't use their version

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