3

I want to achieve conditional behavior of type. When I put generic in form of object kind (array, object, tuple, record), really any composite type, then I want the type behave as a typeof field of this object, but when the given type is a primary, I want to be the same type.

type B<A> = A extends object ? A[keyof A] : A;
const tuple: [1, 2];
const element:B<typeof tuple> = 2; // # ISSUE - element can be also any method of array

const num: 1;
const numElment:B<typeof num> = 1;

Above code works but for composite B type enables me to assign also all method types from an Array type. My question is - how can I specify that I am interested only about things which are not functions. So only pure fields, as in example with the tuple, element should be only number.

I was trying also with extends {} or extends {[a: string | number]: any} but it was also not working, and even above snippet would break after that.

1 Answer 1

7

If you really want to exclude functions from a type union you can do it with the Exclude<T, U> utility type:

type ExcludeFunctionProps<T extends object> = Exclude<T[keyof T], Function>;

But I don't think this really does what you want:

type Hmm = ExcludeFunctionProps<["a", "b"]>; // "a" | "b" | 2

Here Hmm is "a" | "b" | 2. Why 2? Because arrays have a length property of a numeric type, and a pair tuple has that type as the numeric literal type 2. Unless you intend to include the tuple length, this is probably not the way to go.

type EvenWeirder = ExcludeFunctionProps<[string, () => void]>; // string | 2

And in the case where the tuple or object explicitly contains function-like values, this would also cut them out. Definitely seems like strange behavior to me.


Instead, I think that you're running into the issue where keyof T for an array type is still the full list of all array methods and properties even though you can map over array types without them. So what I'd try here is to make my own KeyofAfterMapping<T> which returns keyof T for non-array types, but only keeps the number index key for array types. Like this:

type KeyofAfterMapping<T> = Extract<
  keyof T,
  T extends any[] ? number : unknown
>;

Let's see what it does:

type KeyofPair = KeyofAfterMapping<["a", "b"]>; // number
type KeyofArray = KeyofAfterMapping<string[]>; // number
type KeyofObject = KeyofAfterMapping<{ a: string; b: number }>; // "a" | "b"

Now we can specify B:

type B<A> = A extends object ? A[KeyofAfterMapping<A>] : A;

And verify that it behaves as you expect:

type TryTupleAgain = B<["a", "b"]>; // "a" | "b"
type KeepsFunctions = B<Array<() => void>>; // () => void
type ObjectsAreStillFine = B<{ a: string; b: number }>; // string | number
type PrimitivesAreStillFine = B<string>; // string

Looks good. Okay, hope that helps. Good luck!

Link to code

Sign up to request clarification or add additional context in comments.

1 Comment

Nice one. For now only +. Need to check if this will work. Thank you

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.