1

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?

1 Answer 1

1

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[]
Sign up to request clarification or add additional context in comments.

3 Comments

Hi, thank you for the explanation. But I realize, I made a mistake in my question: otherProp should of course be: number[] | undefined.
@ghost23 changed to reflect the change in requirements
Thank you so much for your help. But unfortunately now coolProp?: boolean[]; becomes coolProp?: boolean[][]. It should stay as it was.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.