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.
7 Replies
Unknown User
Unknown User•3y ago
Message Not Public
Sign In & Join Server To View
hoshi
hoshiOP•3y 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•3y 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•3y 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•3y ago
Message Not Public
Sign In & Join Server To View
hoshi
hoshiOP•3y 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•2y ago
Message Not Public
Sign In & Join Server To View
Want results from more Discord servers?
Add your server