I have a basic interface, which I use down the line to enforce specific values on other interfaces:
interface ObjectWithEnforcedValues {
values: readonly EnforcedValue[];
}
enum EnforcedValue {
FirstValue = 'FirstValue',
SecondValue = 'SecondValue',
}
Then I use it on an actual interfaces I want to use to create actual objects, for example:
interface Interface extends ObjectWithEnforcedValues {
values: readonly [EnforcedValue.FirstValue];
}
So far so good, but the problem comes when I try to extend the Interface
and increase the number of enforced values. Typescript does not like this at all:
interface ExtendedInterface extends Interface {
values: readonly [EnforcedValue.FirstValue, EnforcedValue.SecondValue]
}
Basically the goal is to make sure anyone creating new instances of objects using these interfaces sets the values
property to the specific enforced values, but I am not able to make it work properly with inheritance. Logically it seems that it SHOULD work, because in the case above, Interface
enforces the first value in the array to be FirstValue
and ExtendedInterface
adheres to that and only adds a SecondValue
to the second position of the array. Is there any way to do this or Typescript just does not allow this? First thing that comes to mind is using class
and just set the property directly there instead of using interfaces like this, but unfortunately I cannot use that, everything is just plain objects.
Things I have tried:
- Defining
ExtendedInterface
usingextends Omit<Interface, 'values'>
and redefiningvalues
- This defies proper inheritance and disallows passingExtendedInterface
instances as a parameter to functions acceptingInterface
- Defining interfaces in question like this:
interface Interface extends ObjectWithEnforcedValues {
values: readonly [EnforcedValue.FirstValue, ...EnforcedValue[]];
}
interface ExtendedInterface extends Interface {
values: readonly [EnforcedValue.FirstValue, EnforcedValue.SecondValue,
...EnforcedValue[]]
}
This makes more or less everything work as I need, but allows to put whatever values at the end of the array, which I do not want to allow.
Is there any way to make it work properly or should I just give up and use the second option?
I have a basic interface, which I use down the line to enforce specific values on other interfaces:
interface ObjectWithEnforcedValues {
values: readonly EnforcedValue[];
}
enum EnforcedValue {
FirstValue = 'FirstValue',
SecondValue = 'SecondValue',
}
Then I use it on an actual interfaces I want to use to create actual objects, for example:
interface Interface extends ObjectWithEnforcedValues {
values: readonly [EnforcedValue.FirstValue];
}
So far so good, but the problem comes when I try to extend the Interface
and increase the number of enforced values. Typescript does not like this at all:
interface ExtendedInterface extends Interface {
values: readonly [EnforcedValue.FirstValue, EnforcedValue.SecondValue]
}
Basically the goal is to make sure anyone creating new instances of objects using these interfaces sets the values
property to the specific enforced values, but I am not able to make it work properly with inheritance. Logically it seems that it SHOULD work, because in the case above, Interface
enforces the first value in the array to be FirstValue
and ExtendedInterface
adheres to that and only adds a SecondValue
to the second position of the array. Is there any way to do this or Typescript just does not allow this? First thing that comes to mind is using class
and just set the property directly there instead of using interfaces like this, but unfortunately I cannot use that, everything is just plain objects.
Things I have tried:
- Defining
ExtendedInterface
usingextends Omit<Interface, 'values'>
and redefiningvalues
- This defies proper inheritance and disallows passingExtendedInterface
instances as a parameter to functions acceptingInterface
- Defining interfaces in question like this:
interface Interface extends ObjectWithEnforcedValues {
values: readonly [EnforcedValue.FirstValue, ...EnforcedValue[]];
}
interface ExtendedInterface extends Interface {
values: readonly [EnforcedValue.FirstValue, EnforcedValue.SecondValue,
...EnforcedValue[]]
}
This makes more or less everything work as I need, but allows to put whatever values at the end of the array, which I do not want to allow.
Is there any way to make it work properly or should I just give up and use the second option?
Share Improve this question asked Mar 18 at 16:36 TomTom 2251 gold badge3 silver badges7 bronze badges 10 | Show 5 more comments1 Answer
Reset to default 1You could use generics:
Playground
interface ObjectWithEnforcedValues<T extends readonly EnforcedValue[] = readonly EnforcedValue[]> {
values: T
}
enum EnforcedValue {
FirstValue = 'FirstValue',
SecondValue = 'SecondValue',
}
interface Interface<T extends readonly EnforcedValue[] = readonly [EnforcedValue.FirstValue]> extends ObjectWithEnforcedValues <T> {
}
interface ExtendedInterface<T extends readonly EnforcedValue[] = readonly [EnforcedValue.FirstValue, EnforcedValue.SecondValue]> extends Interface<T> {
}
const i1: Interface = {values: [EnforcedValue.FirstValue]}
const i2: ExtendedInterface = {values: [EnforcedValue.FirstValue, EnforcedValue.SecondValue]}
To ensure structural inheritance you could use an object instead of an array:
Playground
...EnforcedValue[]
at the end). Closed-ended tuples like[EnforcedValue.FirstValue]
have alength
restriction, so you can't "extend" it by adding stuff to the end. Conversely, open-ended tuples allow you to "put whatever values at the end of the array" which you do want to allow, or elseExtendedInterface
wouldn't be assignable toInterface
. It's very unclear to me what you mean by "extend". Please edit to clarify your use cases to make sense; right now it's contradictory. – jcalz Commented Mar 18 at 17:01values
property and if I keep it open ended, someone could put[EnforcedValue.FirstValue, EnforcedValue.SecondValue]
even into an object of typeInterface
, which should not happen. I want to be able to define contents ofvalues
as strictly as possible, but also be able to use inheritance, where the subtypes would add something to thevalues
property. – Tom Commented Mar 19 at 19:59class
, I cannot useinstanceof
to find out which is which. That is why I want to very strictly define thevalues
, which I can then use to find out which type of object I am working with. – Tom Commented Mar 19 at 20:30extending
an existing interface should contain all values coming from sub interfaces plus one more for the new interface to make it work like usual inheritance. And that is why it is desirable that nobody can freely append stuff at the end of the array for the given type. – Tom Commented Mar 19 at 20:43[EnforcedValue.FirstValue, EnforcedValue.SecondValue]
for an object of typeInterface
, because if that's prevented then anExtendedInterface
would not be anInterface
. Please let me know whether or not you understand this fundamental feature of TypeScript's structural type system. – jcalz Commented Mar 19 at 20:53