Pedro Figueiredo
Pedro Figueiredo

Reputation: 2452

Return type based on generic model in parameters

function getModelDetails<T>(model: Partial<Record<keyof T, unknown>>): Record<keyof typeof model, unknown> {
  return (model as unknown) as Record<keyof typeof model, unknown>;
}
interface Person {
  firstName: string;
  lastName: string;
  age: number;
}

// person is assigned with an object with all the Person properties instead of only "firstName" and "lastName"
const person = getModelDetails<Person>({ firstName: 'jon', lastName: 'smith' });

Is there anyway to have the return type of whatever was passed into the generic function, instead of just returning all the Type's properties?

I also don't want to specify what properties I want to return, I basically wanna find a way to have a return type based on what properties were passed to the function.

I'm not even sure if this is possible, but thanks in advance ;)

Desired

// person only with {firstName:"...", lastName: "..."}
const person = getModelDetails<Person>({ firstName: 'jon', lastName: 'smith' });

Upvotes: 0

Views: 201

Answers (1)

Linda Paiste
Linda Paiste

Reputation: 42288

There are two generic values involved here: the type of the model and the subset of properties that we are passing. You have said that you want to specify the first generic, but allow the second to be inferred. This is not something that you can do with a single function. You would need to use currying, aka a "double function".

function getModelDetails<Model>() {
    return function <Keys extends keyof Model>(model: Pick<Model, Keys>): Pick<Model, Keys> {
        return model;
    }
}

You could use the return type [K in Keys]: Model[K] instead of Pick<Model, Keys>. They mean the same thing but if you write it out manually is then when you hover over the variable you get { firstName: string; lastName: string; } instead of Pick<Person, "firstName" | "lastName">. It's up to you which you prefer.

To use this function you need an extra set of parentheses () to invoke the first function which sets the Model to Person.

// person has type: Pick<Person, "firstName" | "lastName">
const person = getModelDetails<Person>()({ firstName: 'jon', lastName: 'smith' });

// error if adding unsupported property
// TS2345: Object literal may only specify known properties, and 'somethingElse' does not exist in type 'Pick<Person, keyof Person>'
const person2 = getModelDetails<Person>()({firstName: 'jon', lastName: 'smith', somethingElse: '' });

// if you don't set the first generic then you get type Pick<unknown, never> because we don't have any default value
const person3 = getModelDetails()({ firstName: 'jon', lastName: 'smith' });

Typescript Playground Link

Upvotes: 2

Related Questions