Typescript Function to make specific nested keys non nullable?

Does anyone know of a Typescript Function to make a bunch of provided nested keys non-nullable? Sometimes I want to reuse a GQL type but want to remove a bunch of nulls/undefined from nested keys. E.g.
type User = {
id: number | null | undefined;
address:
| {
unitNumber: string | null;
streetNumber: string | null;
street: string | null;
metadata: {
nearPark: boolean | null;
} | null;
}
| null
| undefined;
};

type ValidatedUser = NonNullableByPath<
User,
"id" | "address.streetNumber" | "address.street" | "address.metadata"
>;
type User = {
id: number | null | undefined;
address:
| {
unitNumber: string | null;
streetNumber: string | null;
street: string | null;
metadata: {
nearPark: boolean | null;
} | null;
}
| null
| undefined;
};

type ValidatedUser = NonNullableByPath<
User,
"id" | "address.streetNumber" | "address.street" | "address.metadata"
>;
I came up with this so far but I wouldn't be surprised if someone already knows of a better solution: https://www.typescriptlang.org/play?#code/C4TwDgpgBA4hwCUD2TgAUCGwAWAeAKgHxQC8U+UEAHsBAHYAmAzlAAYAkA3gJZ0BmEAE5RkqAL4A6Lk2CDeAczGsoAfhEpgUAFxQ6EAG5CA3AChQkWPAByEGRAaYcAdW45RwXCajkANF-WolDT0zFAycnTyJsRkFNS0jCwcnO6SXLwCwjZ2DEqqUNm0DNq6BsYmJgD0lQUQ9lDASFDcALZggkiGzZpMTa5Q2BiMADa2UEhgwNxIdBjDUADWECBMKlU1TkgArsPFAEbQ8oIQWM18UI7YLPEhLIX2lyy8dhjFSOfhCuu19Y1Qxy1OtAWhAWgdBCx3hcsNgGoNNBhjlAMFAAERMLZ7AC0YBhqIkUAAohJ5ATuOdLqQ0RhUVAAD7UiR7WncFjtTrcBj2Hz-UFA6moszgaBWGZWHbDDB7UYAIRAlwIPMpN0SYVkChiUE4-gA2gBpZp0RbLKH4AC6OgNKtCl383jUoro4uGkulEAI+rNhDtJStwVVcEQGgVl293ntBTFEqlsvlMNwjudrtGHr1Xp5gfuDhhLjcGlwlx5esIYfDOnwntMYlMQosAFUmEIqdrvJydHQtmCmwyOy76VAtowIHxePZTN5XgxjkwmFofQyW+Gl4PXOKu4IdJ9Iv3e8Nx0vw+E6sA1+DN+rtz2JfuD2rjvBzxF5Dvrz6lyDgK8sBgdIvb949ERTBBAWHQ9hQUYhhfF0bwPMRoL3N8oDEeddAlVDBy5Ec9AYKsa3MaAADU5k5LB7AbJsyETaM3TlBV-AowQ-G8VFOVpBlUUnacmAkI9rE7cF2OpBgp1sHi+OAITOJE7iJA-L9P0FQhTCAA Also we need a Typescript tag 😉
TS Playground - An online editor for exploring TypeScript and JavaS...
The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.
9 Replies
Unknown User
Unknown User•4y ago
Message Not Public
Sign In & Join Server To View
hoshi
hoshiOP•4y ago
Awesome stuff ! I'll probably wrap NonNullableByPath with an Omit<..., never> to merge the intersection into a single type for a slightly easier to read intellisense hover tooltip
type NonNullableByPath<T, Path extends NestedPaths<T>> =
// removes all the rootPaths from T and leaves them unchanged
Omit<Omit<T, GetRootPath<Path>>
// for each of the keys that are affected by `Path`
& {
// the `-?` makes the properties here not optional while keeping potential readonly modifiers
[K in keyof T as Extract<K, GetRootPath<Path>>]-?:
// checks if it is exactly that path
K extends Path
? NonNullable<T[K]>
// if not it recursively goes through the branch
: NonNullableByPath<NonNullable<T[K]>,
// the `&`s here are to make sure the values have the correct type
GetNestedPathWithRoot<Path, K & string> & NestedPaths<NonNullable<T[K]>>>
}, never>
type NonNullableByPath<T, Path extends NestedPaths<T>> =
// removes all the rootPaths from T and leaves them unchanged
Omit<Omit<T, GetRootPath<Path>>
// for each of the keys that are affected by `Path`
& {
// the `-?` makes the properties here not optional while keeping potential readonly modifiers
[K in keyof T as Extract<K, GetRootPath<Path>>]-?:
// checks if it is exactly that path
K extends Path
? NonNullable<T[K]>
// if not it recursively goes through the branch
: NonNullableByPath<NonNullable<T[K]>,
// the `&`s here are to make sure the values have the correct type
GetNestedPathWithRoot<Path, K & string> & NestedPaths<NonNullable<T[K]>>>
}, never>
hoshi
hoshiOP•4y ago
Need to remove members of Path that are a "sub-path". E.g. if Path = "a" | "a.b" is provided, remove "a"
I was thinking about this scenario, where address is redundant. But if it's not filtered out from the Paths union, it results in a type where address.metadata can still be null.
type ValidatedUser2 = NonNullableByPath<
User, "address.metadata" | "address"
>;
type ValidatedUser2 = NonNullableByPath<
User, "address.metadata" | "address"
>;
hoshi
hoshiOP•4y ago
Trying to come up with a typescript function to filter out "subpaths" now. It seems like it should be easy but I'm getting stuck with the peculiarities with how unions are distributed in a condition.
type FilterForA<T extends string, P = T> = P extends P
? P extends "a" ? P : never : never

type FilterForAV2<T extends string, P = T> = P extends P
? Exclude<P, "c"> extends "a" ? P : never : never

type t1 = FilterForA<"a.c" | "a" | "c"> // gives "a"
type t2 = FilterForAV2<"a.c" | "a" | "c"> // gives "c" | "a" for some reason
type FilterForA<T extends string, P = T> = P extends P
? P extends "a" ? P : never : never

type FilterForAV2<T extends string, P = T> = P extends P
? Exclude<P, "c"> extends "a" ? P : never : never

type t1 = FilterForA<"a.c" | "a" | "c"> // gives "a"
type t2 = FilterForAV2<"a.c" | "a" | "c"> // gives "c" | "a" for some reason
Unknown User
Unknown User•4y ago
Message Not Public
Sign In & Join Server To View
hoshi
hoshiOP•4y ago
Wow thanks, really appreciate breaking it down for me like that. I think I understand it a lot more now. I was not expecting never to extend everything else but now that I think about it, it makes sense given that never is an empty set. So of course an empty set is a subset of all other sets. I actually wanted GetUniquePaths to filter out the most "shallow" path and retain the deeper paths. So I wanted this output:
type t1 = GetUniquePaths<"a.c" | "a" | "c"> // => "a.c" | "c"
type t1 = GetUniquePaths<"a.c" | "a" | "c"> // => "a.c" | "c"
Thanks to your explanation, I ended up implementing it like this:
type IsSubPath<Paths extends string, P extends string> = Paths extends unknown
? Paths extends `${P}.${string}`
? true
: false
: never

type GetUniquePaths<T extends string, P extends string = T> = P extends P
? IsSubPath<T, P> extends false
? P
: never
: never


type t1 = GetUniquePaths<"a" | "a.b" | "c" | "c.a" | "a.b.c" | "c.d"> // "c.a" | "a.b.c" | "c.d"
type IsSubPath<Paths extends string, P extends string> = Paths extends unknown
? Paths extends `${P}.${string}`
? true
: false
: never

type GetUniquePaths<T extends string, P extends string = T> = P extends P
? IsSubPath<T, P> extends false
? P
: never
: never


type t1 = GetUniquePaths<"a" | "a.b" | "c" | "c.a" | "a.b.c" | "c.d"> // "c.a" | "a.b.c" | "c.d"
Unknown User
Unknown User•4y ago
Message Not Public
Sign In & Join Server To View
jix74
jix74•3y ago
How do I get on your guys' level with Typescript. Difficult to find any tutorials that discuss any of what you guys are talking about.
Unknown User
Unknown User•3y ago
Message Not Public
Sign In & Join Server To View

Did you find this page helpful?