Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to ensure an array's values the keys of a typescript interface?

Tags:

typescript

I am trying to produce an events property on Klass which contains an array of strings that exactly matches all the keys of a given interface. Like so:

interface Events {
    one: (foo: string) => void
    two: (bar: number) => void
}

class Klass {
    protected readonly events: [keyof Events] = ['one', 'two']
}

However, the above errors out with the following:

[ts]
Type '["one", "two"]' is not assignable to type '["one" | "two"]'.
  Types of property 'length' are incompatible.
    Type '2' is not assignable to type '1'. [2322]
(property) Klass.events: ["one" | "two"]

What is needed here to ensure that the events property returns an array that contains all of the events?

like image 410
balupton Avatar asked Nov 20 '18 07:11

balupton


People also ask

How do you get keys in TypeScript?

To get an object's key by value in TypeScript: Use the Object. keys() method to get an array of the object's keys. Type the array to be an array of the object's keys.

How do I create a key value pair in TypeScript?

Use an index signature to define a key-value pair in TypeScript, e.g. const employee: { [key: string]: string | number } = {} .

How do I iterate through interface TypeScript?

To iterate over interface properties in TypeScript, we can use the Object. keys method. const Activity = { id: "", title: "", body: "", json: {}, }; type Activity = typeof Activity; const headers: Array<Object> = Object. keys(Activity).

What TypeScript keyword allows an interface to inherit from another interface?

TypeScript Interface Inheritance Like JavaScript classes, an interface can inherit properties and methods from another interface using the extends keyword.


2 Answers

You can almost express this in the type system (assuming TS3.0+) with conditional types, with a few caveats:

type Invalid<T> = ["Needs to be all of", T]
const arrayOfAll = <T>() => <U extends T[]>(
  ...array: U & ([T] extends [U[number]] ? unknown : Invalid<T>[])
) => array;
const arrayOfAllEventKeys = arrayOfAll<keyof Events>();

const goodEvents = arrayOfAllEventKeys('one', 'two'); // okay, type ['one', 'two']

const extraEvents = arrayOfAllEventKeys('one', 'two', 'three'); // error
//                                                    ~~~~~~~ 
// Argument of type "three" is not assignable to parameter of type "one" | "two"

const missingEvents = arrayOfAllEventKeys('one'); // error
//                                        ~~~~~ 
// Argument of type "one" is not assignable to 
// parameter of type ["Needs to be all of", "one" | "two"]

const redundantEvents = arrayOfAllEventKeys('one', 'two', 'one'); // no error
// doesn't enforce distinctness

Note that goodEvents is inferred to be of type ['one', 'two'], and there is no error. That's what you want. You get errors on extra events and on missing events.

Caveat 1: The error for missing events is a bit cryptic; TypeScript doesn't yet support custom error messages, so I chose something that hopefully is somewhat understandable (Argument of type "one" is not assignable to parameter of type ["Needs to be all of", "one" | "two"]).

Caveat 2: There is no error for redundant events. There's no general way that I can find to require that each parameter to arrayOfAllEventKeys is of a distinct type that doesn't run afoul of some issues with recursive types. It's possible to use overloading or other similar techniques to work for arrays of up to some hardcoded length (say, 10), but I don't know if that would meet your needs. Let me know.

Hope that helps; good luck!

like image 105
jcalz Avatar answered Nov 15 '22 10:11

jcalz


You are putting the [] in the wrong place.

interface Events {
  one: (foo: string) => void
  two: (bar: number) => void
}

class Klass {
  protected readonly events: (keyof Events)[] = ['one', 'two']
}

Notice how I change [keyof Events] to (keyof Events)[].

Check this typescript playground to verify that there are no errors.

like image 31
Aaron Turkel Avatar answered Nov 15 '22 11:11

Aaron Turkel