Reputation: 2242
I have objects with properties. These properties can either be single valued, or list valued and some properties are optional, means, they could be undefined. So here are some example objects:
type A = {
myProp: string;
otherProp?: number;
}
type B = {
coolProp?: boolean[];
someProp: SomeOtherType[];
}
edit:
Now, I implemented a function, that takes such an object and the name of a property and returns that property as it was, for one exception: If the property is NOT optional AND NOT an array, it returns that property as an array with the original type as an array type.
Now, I implemented a function, that takes such an object and the name of a property and returns that property as it was, for one exception: If the property is NOT an array, it returns that property as an array with the original type as an array type.
So for example, properties from type B from above would not change, but myProp: string;
from type A would become: myProp: string[];
. otherProp?: number
would also stay the same.
So for example, properties from type B from above would not change, but myProp: string;
from type A would become: myProp: string[];
. otherProp?: number
would become otherProp?: number[]
.
Now I want to put this behaviour into a return type definition in an interface, but I cannot for the life of me figure out, how to make it work.
My best attempt was:
getPropFromObject<O, P extends keyof O>(node: O, propName: P):
any[] extends O[P] ? O[P] : O[P] extends undefined ? O[P] : O[P][]
but it fails for single optional props like otherProp?: number;
How would a correct type definition look like?
Upvotes: 1
Views: 1416
Reputation: 250316
The type relation when dealing with unions might be inverse to what you expect it to be. A union is the base type for any of its members. So for example:
type N = number | undefined extends undefined ? "Y" : "N" //No, the union does not extend a member
type Y = undefined extends number | undefined ? "Y" : "N" // Yes the union extends a member
This might be surprising at first, but if you thing about types in terms of sets it makes sense. The base type is a set that includes all sets representing sub type (after all any subtype instance should also be an instance of the base type).
So coming back to your question, if you want to test if undefined
is in a union with other types for a property, you need to write : undefined extends O[P]
declare function getPropFromObject<O, P extends keyof O>(node: O, propName: P):
any[] extends O[P] ? O[P] : undefined extends O[P] ? O[P] : O[P][]
type A = {
myProp: string;
otherProp?: number;
}
getPropFromObject(null as any as A, "otherProp") // number | undefined
getPropFromObject(null as any as A, "myProp") // string[]
Edit
After the change in the question, to achieve the effect you want, you can use a distributive conditional type. This will distribute over unions such as number | undefined
with the conditional type being applied to each member of the union.
type ToArray<T> = T extends unknown ? T extends undefined ? T : T[] : never;
declare function getPropFromObject<O, P extends keyof O>(node: O, propName: P):
any[] extends O[P] ? O[P] : ToArray<O[P]>
type A = {
myProp: string;
otherProp?: number;
}
getPropFromObject(null as any as A, "otherProp") // number[] | undefined
getPropFromObject(null as any as A, "myProp") // string[]
Upvotes: 1