2

I'd like to define an interface with optional string values. Something like:

interface IEntity {
    values: ['RemainingUnits', 'ActualUnits', 'PlannedUnits']
}

But with this interface I have problems:

const entity0: IEntity = { values: ['PlannedUnits'] }; // => Error
const entity1: IEntity = { values: ['RemainingUnits', 'ActualUnits'] }; // => Error
const entity2: IEntity = { values: ['PlannedUnits', 'RemainingUnits', 'ActualUnits'] }; // => Error

So are there ways to write the correct interface to avoid errors above?

And perfectly without duplicated strings and not empty

zemil
  • 3,235
  • 2
  • 24
  • 33
  • 2
    Possible duplicate of [Is there a way to define type for array with unique items in typescript?](https://stackoverflow.com/questions/57016728/is-there-a-way-to-define-type-for-array-with-unique-items-in-typescript) – jcalz May 18 '20 at 17:58
  • Does not seem like a duplicate to me. It's a different requirement to make an array of arbitrary unique numbers than it is to make one of string literals that are know in advance. – Suonto Jun 27 '23 at 17:09

5 Answers5

5

Maybe:

type Units = 'RemainingUnits' | 'ActualUnits' | 'PlannedUnits';

interface IEntity {
  values?: Units[];
}
Gilson Cavalcanti
  • 1,483
  • 1
  • 12
  • 18
2

You can try to use an <> for the specific string.

interface IEntity {
    values: Array<'RemainingUnits' | 'ActualUnits' | 'PlannedUnits'>
}

Also, inspired by Nenroz, I think you can make use of type to group string as well. Good to use when you have many different stuffs.

type Units = 'RemainingUnits' | 'ActualUnits' | 'PlannedUnits';

interface IEntity {
  values: Array<Units> ;
}
tcf01
  • 1,699
  • 1
  • 9
  • 24
  • 1
    Syntax `Array<>` is not recommended. Typescript recommends the use of brackets `Units[]`. – VRoxa May 19 '20 at 01:16
  • 2
    Really? Any source for reference? – tcf01 May 19 '20 at 02:01
  • 2
    Well, my comment is based on the TS compiler suggestions. It is something about `readonly` which is only allowed with brackets syntax. You can read this [here] (https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#caveats) – VRoxa May 19 '20 at 02:07
  • 2
    Thanks for the material. I didn't realize it before :) – tcf01 May 19 '20 at 02:16
1

I don't think this is what the type system is for. By enforcing this as a compile-time rule (which is all Typescript's typings ever are), you're ensuring that Typescript will only allow values that it can completely determine at compile time. This would disallow in-place modification of an array, even one that would otherwise fit your rules. The usability cost of this interface would likely exceed the error-catching value it would give your API's consumers.

let array: Units = ['RemainingUnits'];
if (condition) {
  array.push('ActualUnits');  // not allowed; Typescript can't reason about it
}
return array;

Besides, Javascript has some effective ways to enforce non-duplicate behavior (sets or object keys) that would be very natural to what you want, and would also allow runtime behaviors that users would want (like being able to modify the list before passing it in).

type Units = {
  RemainingUnits: boolean,
  ActualUnits: boolean,
  PlannedUnits: boolean
}

If you really want this, you'll need to spell it out:

type A = 'RemainingUnits';
type B = 'ActualUnits';
type C = 'PlannedUnits';

type Units = [A] | [B] | [C]
  | [A, B] | [A, C] | [B, A] | [B, C] | [C, A] | [C, B]
  | [A, B, C] | [A, C, B] | [B, A, C] | [B, C, A] | [C, A, B] | [C, B, A];

interface IEntity {
  values: Units;
}

const example1: IEntity = { values: ['RemainingUnits', 'PlannedUnits'] }
const example2: IEntity = { values: ['RemainingUnits', 'RemainingUnits'] }  //error
const example3: IEntity = { values: [] }  //error

typescript playground

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
0

It should fit for your case :

type Units = 'RemainingUnits' | 'ActualUnits' | 'PlannedUnits';

interface IEntity {
    values: [Units, Units?, Units?]
}

const entity0: IEntity = { values: ['PlannedUnits'] }; // success
const entity1: IEntity = { values: ['RemainingUnits', 'ActualUnits'] }; // success
const entity2: IEntity = { values: ['PlannedUnits', 'RemainingUnits', 'ActualUnits'] }; // success
zronn
  • 1,026
  • 8
  • 26
0

This is a fixed length array type (aka tuple) with optional literal string types in fixed positions.

Does not tolerate duplicates. The only downside that I see is that you have to begin with undefined if you only want to supply the latter values. There's no way around that.

interface IEntity {
  values: ['RemainingUnits'?, 'ActualUnits'?, 'PlannedUnits'?];
}

Example:

const myIEntity: IEntity = { values: [undefined, 'ActualUnits'] };
Suonto
  • 59
  • 4