Reputation: 1984
This is a follow up question to this. Here the object can have optional parameters and the undefinedAllLeadNodes will like below
Input:
class Person {
name: string = 'name';
address: {street?: string, pincode?: string} = {};
}
const undefperson = undefinedAllLeadNodes(new Person);
console.log(undefperson);
Output:
Person: {
"name": undefined,
"address": undefined
}
As you can see as address has no properties, it should return as undefined.
How can I make sure Undefine(defined here) type handles this? Currently it accepts undefperson.address.street = '';
But I want to let it throw an error with "address may be undefined"
Update:
export function undefineAllLeafProperties<T extends object>(obj : T) {
const keys : Array<keyof T> = Object.keys(obj) as Array<keyof T>;
if(keys.length === 0)
return undefined!;//This makes sure address is set to undefined. Now how to identify this with typescript conditionals so that when accessing undefperson.address.street it should say address may be undefined.
keys.forEach(key => {
if (obj[key] && typeof obj[key] === "object" && !Array.isArray(obj[key])) {
obj[key] = undefineAllLeafProperties(<any>obj[key]) as T[keyof T];
} else if(obj[key] && Array.isArray(obj[key])) {
obj[key] = undefined!;
} else {
obj[key] = undefined!;
}
});
return obj;
}
Upvotes: 0
Views: 1079
Reputation: 474
You can update your class Person to following and it should work.
class Person {
name: string = 'name';
address: { street?: string, pincode?: string } | undefined = { street: 'street', pincode: '44555' };
}
Find the Playground Link
You can do it differently like below as well
type Undefine<T extends object> = Id<{
[K in keyof T]: T[K] extends object ? Undefine<T[K]> | undefined : T[K] | undefined
}>
Find the Playground Link
type Person {
name?: string
address?: {
street?: string
pincode?: string
}
}
is equivalent to
type Person {
name: string | undefined
address: {
street: string | undefined
pincode: string | undefined
} | undefined
}
In typescript ?
is used for denoting optional param which is basically undefined
Upvotes: 0
Reputation: 8340
First we'll need a couple of helper types:
type HasNoKeys<T extends object> = keyof T extends never ? 1 : 0
type RequiredOnly<T> = {
[K in keyof T as T[K] extends Required<T>[K] ? K : never]: T[K]
}
The first one check whether the passed T
object has no keys. The second one is a bit more complex. We're using remappine kyes in mapped types feature to remove optional fields.
And finally combile those type to result in undefined
only when T
object has no required fields (or no fields at all, because object having no fields have no required fields aswell):
type UndefinedIfHasNoRequired<T> =
HasNoKeys<RequiredOnly<T>> extends 1 ? undefined : never
And the final type will look like:
type Undefine<T extends object> = {
[K in keyof T]: T[K] extends object
? Undefine<T[K]> | UndefinedIfHasNoRequired<T[K]>
: T[K] | undefined
}
Here we're adding | undefined
to the type of the object field only if it has no required fields.
Though now you'll have to assure typescript that your inner properties are not undefined
when trying to assign values to their fields:
class OptionalPerson {
name: string = 'name';
address: {street?: string, pincode?: string} = {street: 'street', pincode: '44555'};
}
const undefOptionalPerson = undefineAllLeafProperties(new OptionalPerson())
undefOptionalPerson.address.street = '' // error
undefOptionalPerson.address!.street = ''
// or
if (undefOptionalPerson.address) {
undefPerson.address.street = ''
}
In the first case we're using non-null assertion operator to make typescript believe our object's address
field is not undefined
. Though keep in mind that if in fact it's still undefined
you'll get runtime error here.
In the second case we're using legit type narrowing to actually check whether the field has truthy
value. And accounting for it's type object | undefined
this check discards the | undefined
part.
Upvotes: 2