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

2
  • 2
    Possible duplicate of Is there a way to define type for array with unique items in typescript? Commented May 18, 2020 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. Commented Jun 27, 2023 at 17:09

5 Answers 5

5

Maybe:

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

interface IEntity {
  values?: Units[];
}
Sign up to request clarification or add additional context in comments.

Comments

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> ;
}

3 Comments

Syntax Array<> is not recommended. Typescript recommends the use of brackets Units[].
Really? Any source for reference?
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] (typescriptlang.org/docs/handbook/release-notes/…)
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

Comments

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

2 Comments

What about not duplicated fields? :)
@zemil well seen ! didn't though about that
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'] };

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.