最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - TypeScript object set with unique property - Stack Overflow

programmeradmin0浏览0评论

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 badges
Add a ment  | 

3 Answers 3

Reset to default 5

This 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.

发布评论

评论列表(0)

  1. 暂无评论