1

Let's say I have the following interface:

interface Option {
  options: string[];
  symbolOption: string;
}

How can I enforce that the symbolOption must be included in the options array?

Example of what I need

This will be OK:

const option: Option = {
  options: ['a', 'b'],
  symbolOption: 'a' // ✅ OK
};

But this will not be OK:

const option: Option = {
  options: ['a', 'b'],
  symbolOption: 'c' //  `c` is not included in the `options` list.
};
Luiz Felipe
  • 869
  • 12
  • 21
  • Does [this](https://www.typescriptlang.org/play/#code/JYOwLgpgTgZghgYwgAgPIAczAPYgMK4DOYUcoYAPAFDLIAqyEAHpCACaHLFSgDmANDTSMWEdpwxZcBENzLgKdfmgB8VFcgDeQ7JhyyAXPQDaAXQDcQwgE8AtgCNsAG0n6jqYwCJdU2Z9PGIACuDtAWVAC+VFQIRGDIcISuuMgAvMiKIqwcXCR8yqhZYjnJ+HGk5IoFKioAFNjuAJRparTYllQA9J30ABbAnADuwE5OyPYoqADSBjFxyD76AOLY2GxpCUl6uLXabduGyF5wnsqe9v6CtDYOzqVGnifI3ciAoORoU5GNHS8AQkHxMD9IYjMYgbDxCYfWaxWTxRa4X5wdbpRKlXY6A6EIzHU7Ic6XKx2RwuA4PBCeZ49QC8G4AWXeQAAMEAzkANkOD4qAEE4gmwIOtQMggSgGQjZCynAMwAA6L6WIA) work for you? – jcalz Jul 02 '19 at 00:33

2 Answers2

1

This works (playground) though I am not sure whether providing the generic arguments of 'a' | 'b' will suit your requirement or not.

interface Option<T> {
    options: T[];
    symbolOption: T;
}

// This will be OK:
const optionGood: Option<'a' | 'b'> = {
    options: ['a', 'b'],
    symbolOption: 'a' // ✅ OK
};

// But this will not be OK:
const optionBad: Option<'a' | 'b'> = {
    options: ['a', 'b'],
    symbolOption: 'c' //  `c` is not included in the `options` list.
};

Here is another alternative courtesy of jcalz (playground).

interface OptionConstraint<
  T extends string,
  O extends OptionConstraint<T, O>
> {
  options: T[];
  symbolOption: O["options"][number];
}

const asOption = <T extends string, O extends OptionConstraint<T, O>>(o: O) =>
  o;

// This will be OK:
const optionGood = asOption({
  options: ["a", "b"],
  symbolOption: "a" // ✅ OK
});

// But this will not be OK:
const optionBad = asOption({
  options: ["a", "b"],
  symbolOption: "c" //  `c` is not included in the `options` list.
});
Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
  • 3
    Once could use [generic type inference](https://www.typescriptlang.org/play/#code/JYOwLgpgTgZghgYwgAgPIAczAPYgMK4DOYUcoYAPAFDLIAqyEAHpCACaHLFSgDmANDTSMWEdpwxZcBENzLgKdfmgB8VFcgDeQ7JhyyAXPQDaAXQDcQwgE8AtgCNsAG0n6jqYwCJdU2Z9PGIACuDtAWVAC+VFQIRGDIcISuuMgAvMiKIqwcXCR8yqhZYjnJ+HGk5IoFKioAFNjuAJRparTYllQA9J30ABbAnADuwE5OyPYoqADSBjFxyD76AOLY2GxpCUl6uLXabduGyF5wnsqe9v6CtDYOzqVGnifI3ciAoORoU5GNHS8AQkHxMD9IYjMYgbDxCYfWaxWTxRa4X5wdbpRKlXY6A6EIzHU7Ic6XKx2RwuA4PBCeZ49QC8G4AWXeQAAMEAzkANkOD4qAEE4gmwIOtQMggSgGQjZCynAMwAA6L6WIA) to get a similar result; not sure if that's what OP wants though. – jcalz Jul 02 '19 at 00:31
0

If I understand what you are asking, it would be as simple as:

interface Option {
  options: Array<'a'|'b'>;
  symbolOption: string;
}

Edit Edit based on comments.

What you are asking isn't possible, TypeScript is largely a compile type check, you can't have a dynamic type value to represent an interface type.

Have a read of How to check the object type on runtime in TypeScript? to understnad what can and can't be done at runtime

Chris
  • 54,599
  • 30
  • 149
  • 186
  • This does not enforce `symbolOption` to be a member of the `options` array. In addition to that, I don't want to create a hard-coded array type. – Luiz Felipe Jul 01 '19 at 23:13
  • Is it really impossible? Because in TS objects, I can do a similar thing with generics and `keyof T`... Anyways. PS: The downvote isn't mine. – Luiz Felipe Jul 01 '19 at 23:15
  • All good, haven't had my coffee yet. `keyof T` is different though isn't it? T would be defined at compile time? I'd be very surprised if it is possible – Chris Jul 01 '19 at 23:22