1

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!

malcoauri
  • 11,904
  • 28
  • 82
  • 137
  • 1
    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) – T.J. Crowder Mar 02 '22 at 08:16

2 Answers2

1

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

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • I found a way to add `type` back to the objects in `elements`. If you don't see that above, hit refresh, it was a ninja edit. :-) – T.J. Crowder Mar 02 '22 at 07:31
  • elements is array, sorry ( Object is not possible – malcoauri Mar 02 '22 at 07:32
  • @malcoauri - Then I don't think you can do it, except with a fixed-length tuple as jeremy-denis points out. But I'm curious: Why can't it be an object? You can always use `Object.values` when you need an array. – T.J. Crowder Mar 02 '22 at 07:46
  • @malcoauri - Actually, you may be able to do it by adapting [this answer](https://stackoverflow.com/a/64519702/157247). But it's complicated. But I'm still curious about the array requirement. – T.J. Crowder Mar 02 '22 at 08:33
0

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' },
  ],
}
jeremy-denis
  • 6,368
  • 3
  • 18
  • 35