0

I have an instance of my db user model and an array with a list of an object properties:

interface User = {
  firstName: string,
  lastName: string,
  isActive: boolean,
  phone: string,
  // and a lot of other extra properties omitted here
}

const selectedFields = ['firstName', 'lastName'];

I'm trying to use typescript generics to build a new type with the original class and only the selected properties:

const selectedUserInterface = {
  firstName: string,
  lastName: string,
}

I know I can use "as const" like described here ( TypeScript: Define a union type from an array of strings )

const fruits = ["Apple", "Orange", "Pear"] as const;
type Fruit = typeof fruits[number]; // "Apple" | "Orange" | "Pear"

But what I want is to write a generic type, something like an utility type, that return the same without using "as const" and without repeating "typeof ....".

I tried this but it's not working:

type SelectProps<T extends object, K extends (keyof T) & string[]> = Pick<T, K>;

As a stepback (just to obtain literal type from array) I tried this, but can't understand why it's not working

type ArrayToUnion<T extends readonly any[]> = typeof T[number];

Can anyone explain and guide me to the solution?

2
  • 1
    Is this what you are looking for? tsplay.dev/mAgk4w If so, then I will write up that answer. If not then a minimal reproducible example will help a lot here to understand what you want. Commented Jul 31, 2023 at 20:51
  • It works! But is there a way to avoid repeating "typeof" all the time, including it inside the generic type? (instead of writing type TestA = SelectProps<User, typeof selectedFields> I'd like to write type TestA = SelectProps<User, selectedFields> ) Commented Jul 31, 2023 at 21:01

1 Answer 1

1

You almost had your type right.

You had this:

K extends (keyof T) & string[]

Which means that K must be a keyof T and also a string[]. But what you actually want is that K must be a (keyof T & string)[], that is an array where all members are a key of T and are also a string.

I think you want this:

type SelectProps<T, K extends readonly (keyof T)[]> = Pick<T, K[number]>;

You don't really need the extends object, or the & string here.


Now to make this work the type system must know what keys are provided. That means that this will not work:

const selectedFields = ['firstName', 'lastName'] // inferred as type: string[]

So you can as const there

const selectedFields = ['firstName', 'lastName'] as const
// inferred as type: readonly ['firstName', 'lastName']

Or you can use satisfies:

const selectedFields = ['firstName', 'lastName'] satisfies (keyof User)[]
// inferred as type: ('firstName' | 'lastName)[]

So now Typescript knows the prop names in the type system and can work with them.


is there a way to avoid repeating "typeof" all the time.

A value is data your code can use at runtime. A type is a constraint that describe the structure of your values. You can't use a value as a type. And how you use the type of a value is with the typeof operator. So, no.

But you can declare it once and save it as a type alias:

const selectedFields = ['firstName', 'lastName'] satisfies (keyof User)[]
type UserSelectedFields = typeof selectedFields

Putting all that together it looks like this:

interface User {
  firstName: string,
  lastName: string,
  isActive: boolean,
  phone: string,
  // and a lot of other extra properties omitted here
}

const selectedFields = ['firstName', 'lastName'] satisfies (keyof User)[]
type UserSelectedFields = typeof selectedFields

type SelectProps<T extends object, K extends (keyof T)[]> = Pick<T, K[number]>;

type TestA = SelectProps<User, UserSelectedFields>
//   ^? { firstName: string, lastName: string }

See Playground

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

Comments

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.