1

Is it possible to define a type that is a name of another's type field but only if the field is a string?

interface Person {
    name: string;
    age: number;
}

type StringField<T> = keyof T & T[keyof T]: string; // This doesn't work. What should I put here?

function f<T>(obj: T, field: StringField<T>) {
    return obj[field].length;
}

f<Person>({name: "Bill", age: 42}, "name"); // This should work
f<Person>({name: "Bill", age: 42}, "age"); // This should return an error at compile time
Daniel San
  • 1,937
  • 3
  • 19
  • 36

2 Answers2

0

I would write your code like this:

type KeysMatching<T, V> = { [K in keyof T]: T[K] extends V ? K : never }[keyof T];

interface Person {
  name: string;
  age: number;
}

function f<T>(obj: T, field: KeysMatching<T, string>): number;
function f<K extends PropertyKey>(obj: Record<K, string>, field: K) {
  return obj[field].length;
}

f<Person>({ name: "Bill", age: 42 }, "name"); // This should work
f<Person>({ name: "Bill", age: 42 }, "age"); // This should return an error at compile time

This question is almost certainly a duplicate though; I'm looking for an existing question/answer pair to point you to for an explanation.

Ah, here it is: TypeScript: Accept all Object keys that map to a specific type

jcalz
  • 264,269
  • 27
  • 359
  • 360
0

Repurposed from How can I extract the names of all fields of a specific type from an interface in Typescript?

export interface Person {
  name: string;
  age: number;
}


type ExtractFieldsOfType<O, T> = {
    [K in keyof O]: O[K] extends T ? K : never
}[keyof O];

type StringField<T> = ExtractFieldsOfType<T, string>

function f<T>(obj: T, field: StringField<T>) {
    // Your logic here
}

let person: Person = {
    name: "Bill",
    age: 42,
};

f<Person>({name: "Bill", age: 42}, "name"); // This works
f<Person>({name: "Bill", age: 42}, "age"); // This errors out

Used types from this question

Karthick Vinod
  • 1,237
  • 8
  • 11