diff --git a/src/decorator/array/ArrayUnique.ts b/src/decorator/array/ArrayUnique.ts index 0979aeefc0..1e3de2176c 100644 --- a/src/decorator/array/ArrayUnique.ts +++ b/src/decorator/array/ArrayUnique.ts @@ -11,12 +11,8 @@ export type ArrayUniqueIdentifier = (o: T) => any; export function arrayUnique(array: unknown[], identifier?: ArrayUniqueIdentifier): boolean { if (!Array.isArray(array)) return false; - if (identifier) { - array = array.map(o => (o != null ? identifier(o) : o)); - } - - const uniqueItems = array.filter((a, b, c) => c.indexOf(a) === b); - return array.length === uniqueItems.length; + const values = identifier ? array.map(o => (o != null ? identifier(o) : o)) : array; + return new Set(values).size === values.length; } /** diff --git a/test/functional/validation-functions-and-decorators.spec.ts b/test/functional/validation-functions-and-decorators.spec.ts index 4c266f02ee..1999eebaec 100644 --- a/test/functional/validation-functions-and-decorators.spec.ts +++ b/test/functional/validation-functions-and-decorators.spec.ts @@ -5025,6 +5025,24 @@ describe('ArrayUnique', () => { invalidValues.forEach(value => expect(arrayUnique(value)).toBeFalsy()); }); + it('should compare objects by reference', () => { + const object = { name: 'test' }; + + expect(arrayUnique([object, { name: 'test' }])).toBeTruthy(); + expect(arrayUnique([object, object])).toBeFalsy(); + }); + + it('should use SameValueZero semantics for NaN', () => { + expect(arrayUnique([1, NaN, 2])).toBeTruthy(); + expect(arrayUnique([NaN, NaN])).toBeFalsy(); + }); + + it('should treat sparse array items as undefined', () => { + expect(arrayUnique([, 1])).toBeTruthy(); + expect(arrayUnique([, , 1])).toBeFalsy(); + expect(arrayUnique([undefined, , 1])).toBeFalsy(); + }); + it('should return error object with proper data', () => { const validationType = 'arrayUnique'; const message = "All someProperty's elements must be unique"; @@ -5069,6 +5087,19 @@ describe('ArrayUnique with identifier', () => { invalidValues.forEach(value => expect(arrayUnique(value, identifier)).toBeFalsy()); }); + it('should call the identifier for every non-null value before checking uniqueness', () => { + const visitedNames: string[] = []; + const trackingIdentifier = (value: { name: string }): string => { + visitedNames.push(value.name); + return value.name; + }; + + expect( + arrayUnique([{ name: 'duplicate' }, { name: 'duplicate' }, { name: 'last' }], trackingIdentifier) + ).toBeFalsy(); + expect(visitedNames).toEqual(['duplicate', 'duplicate', 'last']); + }); + it('should return error object with proper data', () => { const validationType = 'arrayUnique'; const message = "All someProperty's elements must be unique";