-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Allow conditionally setting optional properties in a mapped type #36126
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
Comments
From @DanielRosenwasser on Twitter:
So from this I imagine a solution like this:
but this still doesn't work. If I do this:
then the |
Duplicate of #32562 |
I also ran into this limitation. As pointed out #32562, it is possible to workaround this limitation (I'm posting here because I think this issue has a much clearer title/description and is easier to find). Here is an ugly but working solution that will hopefully help someone else: type PickUndefinedKeys<T> = {
[K in keyof T]: undefined extends T[K] ? K : never;
}[keyof T];
type PickRequiredKeys<T> = {
[K in keyof T]: undefined extends T[K] ? never : K;
}[keyof T];
type SelectOptional<T extends {}> = Pick<
T,
Exclude<PickUndefinedKeys<T>, undefined>
>;
type SelectRequired<T extends {}> = Pick<
T,
Exclude<PickRequiredKeys<T>, undefined>
>;
// EXAMPLES
type Test1 = SelectOptional<{one?: string; two: number}>;
// => { one?: string }
type Test2 = SelectRequired<{one?: string; two: number}>
// => { two: number } As a more complex example, say you want to map the types in an object while retaining each property's original optional status: interface IPerson {
name: string
}
type OptionalNameMap<
T extends { [key: string]: IPerson | undefined }
> = {
[P in Exclude<PickUndefinedKeys<T>, undefined>]?: NonNullable<T[P]>['name'];
};
type RequiredNameMap<
T extends { [key: string]: IPerson | undefined }
> = {
[P in Exclude<PickRequiredKeys<T>, undefined>]: NonNullable<T[P]>['name'];
};
type MapNames<T extends { [key: string]: IPerson | undefined }> = RequiredNameMap<T> & OptionalNameMap<T>;
// EXAMPLE
type NameDictionary = MapNames<{one: IPerson, two?: IPerson }>;
// => { one: string, two?: string | undefined } Edit: |
I managed to pull this off with some work: type TestType = {
a: SomeInterface;
b: string;
};
type Intersection<A, B> = A & B extends infer U
? { [P in keyof U]: U[P] }
: never;
type Matching<T, SomeInterface> = {
[K in keyof T]: T[K] extends SomeInterface ? K : never;
}[keyof T];
type NonMatching<T, SomeInterface> = {
[K in keyof T]: T[K] extends SomeInterface ? never : K;
}[keyof T];
type DesiredOutcome = Intersection<
Partial<Pick<TestType, Matching<TestType, SomeInterface>>>,
Required<Pick<TestType, NonMatching<TestType, SomeInterface>>
> I think it would be confusing to have the logic for adding It would be awesome to have this syntax: type Optionalize<T> = {
[P in keyof T](?: T[P] extends SomeInterface): ...;
} But I guess this is not such a common problem to justify the effort. |
I was thinking about this, and I have a syntax proposal that doesn't require any parser change: type MyKeys = 'optionalKey' | 'requiredKey';
type Foo = {
[Key in keyof MyKeys as Key extends 'optionalKey' ? Key | undefined : Key]: string;
}
// Result is
{
optionalKey?: string;
requiredKey: string;
} Note that TS already uses such "trick" with |
Here is a proposal syntax leveraging on type MyKeys = 'optionalKey' | 'requiredKey' | 'readonlyKey' | 'readonlyOptionalKey';
type Foo = {
[Key in keyof MyKeys as Key extends 'optionalKey' ?
Key+? :
Key extends 'readonlyKey' ?
Key+readonly :
Key extends 'readonlyOptionalKey ?
Key+?+readonly :
Key
]: string;
}
// Result is
{
optionalKey?: string;
requiredKey: string;
readonly readonlyKey: string;
readonly readonlyOptionalKey?: string;
} if you use '-' instead of '+' it removes the modifier : type MyKeys = 'optionalKey' | 'requiredKey' | 'readonlyKey' | 'readonlyOptionalKey';
type Foo = {
readonly [Key in keyof MyKeys as Key extends 'requiredKey' ?
Key-?-readonly :
Key extends 'readonlyKey' ?
Key-? :
Key extends 'optionalKey' ?
Key-readonly :
Key
]?: string;
}
// Result is
{
optionalKey?: string;
requiredKey: string;
readonly readonlyKey: string;
readonly readonlyOptionalKey?: string;
} |
By combing export type MakeUndefinedPropertiesOptional<T> = Partial<
Pick<T, PickUndefinedKeys<T>>
> &
Pick<T, PickRequiredKeys<T>>;
type Props = {
params: undefined;
data: undefined;
}
const props: MakeUndefinedPropertiesOptional<Props> = {}; // no error |
+1 to supporting this in a first-class way. My use case is that I want to map keys of a certain value type to a different value type, while preserving the optional-ness of the properties in the original type: // I want to turn this:
export interface BaseComment {
creator: UserReference;
repliers?: UserReference[];
}
// into this:
export interface DisplayComment {
creator: UserID;
repliers?: UserID[];
}
// ideally by doing something like
export type DisplayComment = Display<BaseComment>; Currently I have to splice together 2 mapped types to combine both required vs optional keys in order to "preserve" their optional-ness: // Found this method from https://type-level-typescript.com/articles/how-to-get-optional-keys-from-object-types
export type RequiredKeys<T> = keyof {
[K in keyof T as Omit<T, K> extends T ? never : K]: T[K];
};
export type OptionalKeys<T> = keyof {
[K in keyof T as Omit<T, K> extends T ? K : never]: T[K];
};
export interface BaseComment {
creator: UserReference;
repliers?: UserReference[];
}
export type ConvertReferenceToID<T> =
T extends Array<UserReference> ? UserID[] : T extends UserReference ? UserID : T;
export type Display<T> = { [K in RequiredKeys<T>]: ConvertReferenceToID<T[K]> } & {
[K in OptionalKeys<T>]?: ConvertReferenceToID<T[K]>;
};
export type DisplayComment = Display<BaseComment>; |
Search Terms
conditional optional mapped type
Suggestion
Allow us to conditionally apply
?
to a property in a mapped typeUse Cases
I have a function:
but I want to map the params to
Examples
Suggested approach:
(or something similar)
This is the closest I could come:
but that produces
Playground
Checklist
My suggestion meets these guidelines:
The text was updated successfully, but these errors were encountered: