Home > database >  How to restrict array items using union type with TypeScript?
How to restrict array items using union type with TypeScript?

Time:03-03

There is some event type: For example,

interface Event {
  type: 'a' | 'b' | 'c';
  value: string;
}

interface App {
  elements: Event[];
}

It's not bad, but I could do the following thing:

const app: App = {
  elements: [
    { type: 'a', value: '1' },
    { type: 'a', value: '2' },
  ],
}

But I need that elements array contains objects with uniq type (only one item with type 'a', only one item with type 'b', etc). How could I do it in TypeScript? Thanks in advance!

CodePudding user response:

You might be able to adapt this answer to your situation (your union would be of distinct SomeEvent types, which you can generate from your existing type). It's complicated and I couldn't do it in the time I gave it, but it may be possible.

It would simpler, though, to use an object instead of an array, keyed by the type: elements: {a: { value: "1" } }. (If you need to get an array from it at some point, you can always use Object.values(theApp.elements) to get it, but you can easily loop through objects, not just arrays.)

interface SomeEvent {
  type: 'a' | 'b' | 'c';
  value: string;
}

interface App {
  elements: Partial<{
    [key in SomeEvent["type"]]: Omit<SomeEvent, "type"> & { type: key};
  }>
}

That uses the type as an object key, and requires the object assigned to it to have the same value as typd. (I used the name SomeEvent instead of Event to avoid confusing with DOM events.)

Then this works:

const app1: App = {
  elements: {
    a: { type: 'a', value: '1' },
    b: { type: 'b', value: '2' },
  },
};

but this has an error as desired:

const app2: App = {
  elements: {
    a: { type: 'a', value: '1' },
    a: { type: 'a', value: '2' }, // An object literal cannot have multiple properties with the same name in strict mode.
  },
};

Playground link

CodePudding user response:

one way can be to create an interface for Event with different type a, b or c

you can create an interface that will say your elements property can have one instance of each type

interface EventFilter {
  elements: [EventA?, EventB?, EventC?];
}

The limitation of this solution is that element should be added in the order defined in EventFilter

interface Event {
  type: 'a' | 'b' | 'c';
  value: string;
}

interface EventA extends Event {
  type: 'a';
}

interface EventB extends Event {
  type: 'b';
}

interface EventC extends Event {
  type: 'c';
}

interface EventFilter {
  elements: [EventA?, EventB?, EventC?];
}

const app: EventFilter = {
  elements: [ 
    { type: 'a', value: '1' },
    { type: 'b', value: '1' } 
  ],
}

unfortunately, you will have to list all authorised combine to be able to have a flexible array with element in any order

interface EventFilter {
  elements: [EventA?, EventB?, EventC?] | [EventA?, EventC?, EventB?] 
            | [EventB?, EventA?, EventC?] | [EventB?, EventC?, EventA?]
            | [EventC?, EventA?, EventB?] | [EventC?, EventB?, EventA?];
}

const app: EventFilter = {
  elements: [ 
    { type: 'c', value: '1' },
    { type: 'a', value: '1' },
  ],
}
  • Related