Bruce Collie
Bruce Collie

Reputation: 435

Template type constraints

I'm trying to implement a generic function that accesses two properties of an object with related names. For example, hypothetically:

function example<T, K extends string & keyof T, `${K}Suffix` extends keyof T>(k: K, o: T)
{
  doSomethingWith(o[k], o[`k${Suffix}`]);
}

Is this possible? It may be the case that this is an XY problem, so I'm open to similar solutions that are very different syntactically.

Upvotes: 1

Views: 64

Answers (1)

Daniel Gimenez
Daniel Gimenez

Reputation: 20454

You can come close to achieving what you want by using infer in template literal strings to derive the corresponding property. In the example below, two types are created - Suffixable and SuffixDerived. The conditional type definition for Suffixable uses infer to infer the property name where a property name with Suffix exists. SuffixDerived has a definition that appends "Suffix* to the value.

interface TestInterface {
  pA: string;
  pASuffix: string;
  pB: string;
  pBSuffix: string;
  no: string;
}

// derives properties that will have matching "Suffix" properties.
type Suffixable<T, U = keyof T & string> = (U extends `${infer V}Suffix` ? V : never) & keyof T;
// derives properties that end in "Suffix" and have corresponding properties.
type SuffixDerived<T, U extends Suffixable<T>> = `${(keyof T) & U}Suffix` & keyof T;

function example<T>(k: Suffixable<T>, o: T)
{
  const kSuffix = `${k}Suffix` as SuffixDerived<T, typeof k>;
  console.log(o[k], o[kSuffix]);
}

/*** TEST ***/
const sample: TestInterface = { pA: 'A', pASuffix: 'A+', pB: 'B', pBSuffix: 'B+', no: 'No' };
example('pA', sample); // "A", "A+"
example('pB', sample); // "B", "B+"
// example('no', sample); // Error: Argument of type '"no"' is not assignable to parameter of type '"pA" | "pB"'.

This solution isn't perfect as the suffix key's property type needs to be asserted. When setting the suffix key defined as SuffixDerrived to a concatenated or interpolated string, the compilier treated the source key as a string and not a Suffixable. That is why as SuffixDerived<T, keyof T> is used instead of kSuffix: SuffixDerived<T, keyof T> =.

Upvotes: 2

Related Questions