How to ensure an array's values the keys of a typescript interface?
up vote
0
down vote
favorite
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?
typescript
add a comment |
up vote
0
down vote
favorite
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?
typescript
protected readonly events: Array<keyof Events> = ['one', 'two']
– JB Nizet
Nov 20 at 7:35
@JBNizet unfortunately, that makes['one']
is valid. Requirement is that the array must contain all of the keys, not just any of them.
– balupton
Nov 20 at 7:46
Ah, sorry. I don't have any idea then. Not sure it's even possible.
– JB Nizet
Nov 20 at 7:48
add a comment |
up vote
0
down vote
favorite
up vote
0
down vote
favorite
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?
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?
typescript
typescript
asked Nov 20 at 7:04
balupton
30.9k23104153
30.9k23104153
protected readonly events: Array<keyof Events> = ['one', 'two']
– JB Nizet
Nov 20 at 7:35
@JBNizet unfortunately, that makes['one']
is valid. Requirement is that the array must contain all of the keys, not just any of them.
– balupton
Nov 20 at 7:46
Ah, sorry. I don't have any idea then. Not sure it's even possible.
– JB Nizet
Nov 20 at 7:48
add a comment |
protected readonly events: Array<keyof Events> = ['one', 'two']
– JB Nizet
Nov 20 at 7:35
@JBNizet unfortunately, that makes['one']
is valid. Requirement is that the array must contain all of the keys, not just any of them.
– balupton
Nov 20 at 7:46
Ah, sorry. I don't have any idea then. Not sure it's even possible.
– JB Nizet
Nov 20 at 7:48
protected readonly events: Array<keyof Events> = ['one', 'two']
– JB Nizet
Nov 20 at 7:35
protected readonly events: Array<keyof Events> = ['one', 'two']
– JB Nizet
Nov 20 at 7:35
@JBNizet unfortunately, that makes
['one']
is valid. Requirement is that the array must contain all of the keys, not just any of them.– balupton
Nov 20 at 7:46
@JBNizet unfortunately, that makes
['one']
is valid. Requirement is that the array must contain all of the keys, not just any of them.– balupton
Nov 20 at 7:46
Ah, sorry. I don't have any idea then. Not sure it's even possible.
– JB Nizet
Nov 20 at 7:48
Ah, sorry. I don't have any idea then. Not sure it's even possible.
– JB Nizet
Nov 20 at 7:48
add a comment |
2 Answers
2
active
oldest
votes
up vote
1
down vote
I think you need a concrete object to achieve your goal here. In the example below, the implementation of the interface gives you a guaranteed way to get the right list. If you add a new member to Events
it will force you to add it to ConcreteEvents
. The only potential issue would be if you add other members to ConcreteEvents
.
interface Events {
one: (foo: string) => void;
two: (bar: number) => void;
}
class ConcreteEvents implements Events {
// Note! Only add members of Events
one(foo: string) { };
two(bar: number) { };
}
class Klass {
public readonly events = Object.keys(ConcreteEvents.prototype) as any as [keyof Events];
}
const klass = new Klass();
console.log(klass.events);
You could achieve this in several different ways using a concrete class or an object, so you don't have to follow this example exactly.
Brilliant. I'll give it a go tomorrow and will see what I can come up with.
– balupton
Nov 20 at 10:30
add a comment |
up vote
1
down vote
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!
add a comment |
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
1
down vote
I think you need a concrete object to achieve your goal here. In the example below, the implementation of the interface gives you a guaranteed way to get the right list. If you add a new member to Events
it will force you to add it to ConcreteEvents
. The only potential issue would be if you add other members to ConcreteEvents
.
interface Events {
one: (foo: string) => void;
two: (bar: number) => void;
}
class ConcreteEvents implements Events {
// Note! Only add members of Events
one(foo: string) { };
two(bar: number) { };
}
class Klass {
public readonly events = Object.keys(ConcreteEvents.prototype) as any as [keyof Events];
}
const klass = new Klass();
console.log(klass.events);
You could achieve this in several different ways using a concrete class or an object, so you don't have to follow this example exactly.
Brilliant. I'll give it a go tomorrow and will see what I can come up with.
– balupton
Nov 20 at 10:30
add a comment |
up vote
1
down vote
I think you need a concrete object to achieve your goal here. In the example below, the implementation of the interface gives you a guaranteed way to get the right list. If you add a new member to Events
it will force you to add it to ConcreteEvents
. The only potential issue would be if you add other members to ConcreteEvents
.
interface Events {
one: (foo: string) => void;
two: (bar: number) => void;
}
class ConcreteEvents implements Events {
// Note! Only add members of Events
one(foo: string) { };
two(bar: number) { };
}
class Klass {
public readonly events = Object.keys(ConcreteEvents.prototype) as any as [keyof Events];
}
const klass = new Klass();
console.log(klass.events);
You could achieve this in several different ways using a concrete class or an object, so you don't have to follow this example exactly.
Brilliant. I'll give it a go tomorrow and will see what I can come up with.
– balupton
Nov 20 at 10:30
add a comment |
up vote
1
down vote
up vote
1
down vote
I think you need a concrete object to achieve your goal here. In the example below, the implementation of the interface gives you a guaranteed way to get the right list. If you add a new member to Events
it will force you to add it to ConcreteEvents
. The only potential issue would be if you add other members to ConcreteEvents
.
interface Events {
one: (foo: string) => void;
two: (bar: number) => void;
}
class ConcreteEvents implements Events {
// Note! Only add members of Events
one(foo: string) { };
two(bar: number) { };
}
class Klass {
public readonly events = Object.keys(ConcreteEvents.prototype) as any as [keyof Events];
}
const klass = new Klass();
console.log(klass.events);
You could achieve this in several different ways using a concrete class or an object, so you don't have to follow this example exactly.
I think you need a concrete object to achieve your goal here. In the example below, the implementation of the interface gives you a guaranteed way to get the right list. If you add a new member to Events
it will force you to add it to ConcreteEvents
. The only potential issue would be if you add other members to ConcreteEvents
.
interface Events {
one: (foo: string) => void;
two: (bar: number) => void;
}
class ConcreteEvents implements Events {
// Note! Only add members of Events
one(foo: string) { };
two(bar: number) { };
}
class Klass {
public readonly events = Object.keys(ConcreteEvents.prototype) as any as [keyof Events];
}
const klass = new Klass();
console.log(klass.events);
You could achieve this in several different ways using a concrete class or an object, so you don't have to follow this example exactly.
answered Nov 20 at 9:06
Fenton
150k42283308
150k42283308
Brilliant. I'll give it a go tomorrow and will see what I can come up with.
– balupton
Nov 20 at 10:30
add a comment |
Brilliant. I'll give it a go tomorrow and will see what I can come up with.
– balupton
Nov 20 at 10:30
Brilliant. I'll give it a go tomorrow and will see what I can come up with.
– balupton
Nov 20 at 10:30
Brilliant. I'll give it a go tomorrow and will see what I can come up with.
– balupton
Nov 20 at 10:30
add a comment |
up vote
1
down vote
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!
add a comment |
up vote
1
down vote
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!
add a comment |
up vote
1
down vote
up vote
1
down vote
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!
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!
answered Nov 20 at 14:52
jcalz
21k21637
21k21637
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53387838%2fhow-to-ensure-an-arrays-values-the-keys-of-a-typescript-interface%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
protected readonly events: Array<keyof Events> = ['one', 'two']
– JB Nizet
Nov 20 at 7:35
@JBNizet unfortunately, that makes
['one']
is valid. Requirement is that the array must contain all of the keys, not just any of them.– balupton
Nov 20 at 7:46
Ah, sorry. I don't have any idea then. Not sure it's even possible.
– JB Nizet
Nov 20 at 7:48