Reno Reno - 11 months ago 35
TypeScript Question

object interface for fixed number dynamically named keys

How would I define an interface for such objects:

let bob = {
Bob: {
age: 9,
gender: 'M'
}
};
let alice = {
Alice: {
age: 12,
gender: 'F'
}
};


I could use solution from this thread but it is too permissive, I want to restrict that use case to a unique (or fixed number of) key(s).

BONUS if you can figure out how to express:


  • how many keys are allowed in the top level object

  • specify a different interface per key



Something like this pseudo code:

interface funnyEdge {
<string>from: fromEdge,
<string>to: toEdge
}
let edge: funnyEdge = {foo: {...}, bar: {...}}

Answer Source

The type system in TypeScript doesn't allow you to specify a type with a specified number of unspecified keys. In fact, since it lacks exact types, TypeScript doesn't really allow you to specify a type with a specified number of specified keys, either. It only does excess property checking on object literals. As a type, something like:

type Person = {
  age: number,
  gender: 'M' | 'F' | 'O'
}

does not mean a Person can't have other keys, as in:

const pirate = {
  age: 35,
  gender: 'M' as 'M',
  legs: 1,
  parrot: true
};
const piratesArePeopleToo: Person = pirate; // no problem

So the most you could hope for would be that TypeScript complains if you pass in an object literal with too many keys, as in:

const otherPirate: Person = {
  age: 40,
  gender: 'F',
  eyes: 1,
  parrot: false
}; // error

And no, TypeScript doesn't let you express that constraint, as far as I know.


The best I could think of would be a workaround: create a function which takes a single key and a Person, and returns an object of the type you want:

type NamedPerson<K extends string> = Record<K, Person>;
function makeNamedPerson<K extends string>(key: K, person: Person): NamedPerson<K> {
  let namedPerson = {} as NamedPerson<K>;
  namedPerson[key]=person;
  return namedPerson;
}
const namedPerson = makeNamedPerson('chris', { age: 10, gender: 'O' });
console.log(namedPerson.chris.age);

If you organize your code so the only reasonable way to get an object of type NamedPerson<K> is to call the function makeNamedPerson() (e.g., export only the function and make the type it produces a class with a private member), then you know that each one will have a single key. This isn't quite as good for you as being enforced by the type system, but at least you make it difficult for users of your library to circumvent the constraint.

Hope that helps; good luck!

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download