Issue
I need a Partial<T> like mapped type that allows me make nullable fields optional. I'm working on typing our ORM and it coerces undefined to nulls on nullable fields.
I'd like to take a type of
interface User {
email: string
name: string | null
}
And make
interface User {
email: string
name?: string | null
}
I tried
type NullablePartial<T> = { [P in keyof T]: T[P] extends null ? T[P] | undefined : T[P] }
But extends null doesn't work for the union of null and another type (and I'd ideally like it to work with more than strings). And the difference between optional fields and undefined values is important.
What can I do?
Solution
Making a type function turn just some properties optional or non-optional is actually quite annoying in TypeScript, because there's no simple way to describe it. The easiest way I can think to do it is using multiple mapped types with an intersection. Here's something that should work as long as the type you pass in doesn't have an index signature (it gets more complicated):
type NullablePartial<
T,
NK extends keyof T = { [K in keyof T]: null extends T[K] ? K : never }[keyof T],
NP = Partial<Pick<T, NK>> & Pick<T, Exclude<keyof T, NK>>
> = { [K in keyof NP]: NP[K] }
Note that I'm using Pick and Exclude to carve up T into the nullable properties and the non-nullable properties, doing different things to each of them, then intersecting them back together. Determining nullable properties has to do with checking if null extends T[K] (i.e., "can I assign null to this property"), not the reverse (i.e., "can I assign this property to a variable of type null").
I'm also using generic parameter defaults to make the type manipulation more concise (so I only have to determine the nullable property key names once as NP) and to make the final output type more aesthetically pleasing (by doing one final mapped type at the end I get a type that is not represented as an intersection).
Let's try it on:
interface User {
email: string
name: string | null
}
type NPUser = NullablePartial<User>;
// type NPUser = {
// name?: string | null | undefined;
// email: string;
// }
Looks reasonable to me... is that close to what you wanted? Good luck.
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.