i wrote simply interfaces like that:
interface IProduct {
id: number;
name: string;
price: number;
description?: string;
}
Now i want to id
be unique in ReadonlyArray. So when a create a few products, i want to prevent add object with the same id. Array with products will be creating once, in file, and will not be modified.
Have you any idea for that? JS Set will be good solution, but i can't add own parator to them. Please not provide solution which require additional frameworks etc.
i wrote simply interfaces like that:
interface IProduct {
id: number;
name: string;
price: number;
description?: string;
}
Now i want to id
be unique in ReadonlyArray. So when a create a few products, i want to prevent add object with the same id. Array with products will be creating once, in file, and will not be modified.
Have you any idea for that? JS Set will be good solution, but i can't add own parator to them. Please not provide solution which require additional frameworks etc.
Share Improve this question edited Oct 23, 2021 at 16:11 Felix Orinda 7537 silver badges23 bronze badges asked Oct 23, 2021 at 12:11 Szwarceneger16Szwarceneger16 851 silver badge5 bronze badges3 Answers
Reset to default 5This example uses type system to staticaly validate whether there are duplicates or not.
interface IProduct<Id extends number> {
id: Id
name: string;
}
const product = <Id extends number>(id: Id, name: string) => ({ id, name })
type Validation<
Products extends IProduct<number>[],
Accumulator extends IProduct<number>[] = []>
=
(Products extends []
// #1 Last call
? Accumulator
// #2 All calls but last
: (Products extends [infer Head, ...infer Tail]
? (Head extends IProduct<number>
// #3 Check whether [id] property already exists in our accumulator
? (Head['id'] extends Accumulator[number]['id']
? (Tail extends IProduct<number>[]
// #4 [id] property is a duplicate, hence we need to replace it with [never] in order to trigger the error
? Validation<Tail, [...Accumulator, { id: never, name: Head['name'] }]>
: never)
// #5 [id] is not a duplicate, hence we can add to our accumulator whole product
: (Tail extends IProduct<number>[]
? Validation<Tail, [...Accumulator, Head]>
: never)
)
: never)
: never)
)
type Ok = Validation<[{ id: 1, name: '1' }, { id: 2, name: '2' }]>
type Fail = Validation<[{ id: 1, name: '1' }, { id: 1, name: '2' }]> // id:never
const builder = <
Product extends IProduct<number>,
Products extends Product[]
>(...products: [...Products] & Validation<Products>) => products
builder(product(1, 'John'), product(2, 'Doe'))
Playground
Validation
- iterates recursively through all passed into function products. If product[id]
already exists in accumulator type - replace id
property with never
, otherwise just add product to accumulator.
Please see the ments #1
, #2
....
If you dont want to use rest
operator, consider this example:
interface IProduct<Id extends number> {
id: Id
name: string;
}
const product = <Id extends number>(id: Id, name: string) => ({ id, name })
type Validation<
Products extends IProduct<number>[],
Accumulator extends IProduct<number>[] = []>
=
(Products extends []
// #1 Last call
? Accumulator
// #2 All calls but last
: (Products extends [infer Head, ...infer Tail]
? (Head extends IProduct<number>
// #3 Check whether [id] property already exists in our accumulator
? (Head['id'] extends Accumulator[number]['id']
? (Tail extends IProduct<number>[]
// #4 [id] property is a duplicate, hence we need to replace it with [never] in order to trigger the error
? Validation<Tail, [...Accumulator, { id: never, name: Head['name'] }]>
: 1)
// #5 [id] is not a duplicate, hence we can add to our accumulator whole product
: (Tail extends IProduct<number>[]
? Validation<Tail, [...Accumulator, Head]>
: 2)
)
: 3)
: Products)
)
type Ok = Validation<[{ id: 1, name: '1' }, { id: 2, name: '2' }]>
type Fail = Validation<[{ id: 1, name: '1' }, { id: 1, name: '2' }]> // id:never
const builder = <
Id extends number,
Product extends IProduct<Id>,
Products extends Product[]
>(products: Validation<[...Products]>) => products
builder([product(1, 'John'), product(1, 'John')]) // error
Playground
If you are interested in static validation, you can check my article
Do you want pile-time guarantees for this? I doubt this would be possible in TypeScript. Edit: Maybe it is possible after seeing the other answer.
However, the JavaScript code is quite simple:
let products = new Map();
let product = {
id: 1,
name: "Foo",
price: 99,
description: "Bar",
};
// This will update an existing item if it has the same ID
products.set(product.id, product);
// Alternatively, you can check if one already exists in keep the original item
if (!products.has(product.id)) {
products.set(product.id, product);
}
You could wrap this code in your own class with a set of functions:
class ProductList {
constructor() {
this.items = new Map();
}
add(product) {
if(!this.items.has(product.id)) {
this.items.set(product.id, product);
}
}
values() {
// iterate over values
// this will respect insertion order
return this.items.values();
}
// any other methods you'd like...
}
If you need random access by index, you could store an array in your class which is kept in-sync/updated alongside your set. This shouldn't be too difficult to code up.
You were mentioning ReadonlyArray
. I think you can try to make your class implement this interface, if that's something you desire.
this question is answered before but it's related to this link in some way and might be usefull.
It's for unique ids for objects in another object and not in array.