@@ -2,6 +2,7 @@ package validator
22
33import (
44 "context"
5+ "encoding/json"
56 "fmt"
67 "reflect"
78
@@ -325,25 +326,42 @@ func (c *arrayValidator) evaluate(ctx context.Context, v any, st *evalState) (Re
325326 return nil , fmt .Errorf (`invalid value passed to ArrayValidator: array length %d exceeds maximum items %d` , length , * c .maxItems )
326327 }
327328
328- // Check uniqueItems constraint
329- if c .uniqueItems && length > 0 {
329+ // Check uniqueItems constraint.
330+ //
331+ // Rather than the naive O(n^2) pairwise comparison, bucket items by their
332+ // canonical JSON encoding and only compare within a bucket. reflect.DeepEqual
333+ // implies identical json.Marshal output, so the key never yields a false
334+ // negative (no missed duplicate); it may collide for DeepEqual-unequal values
335+ // (e.g. native int(1) vs float64(1.0), both encode as "1"), which is why a real
336+ // duplicate is still confirmed with reflect.DeepEqual to preserve existing
337+ // semantics. For JSON-decoded data (the untrusted path) a shared key implies
338+ // equality, so the first within-bucket comparison returns immediately and the
339+ // scan stays linear.
340+ if c .uniqueItems && acc .length > 1 {
341+ // json.Marshal never returns empty bytes for a valid value, so this
342+ // sentinel cannot collide with a real key. Items that fail to marshal
343+ // (exotic non-JSON values from reflection or a custom ArrayIndexResolver)
344+ // land here and are compared among themselves.
345+ const unmarshalableKey = "\x00 unmarshalable"
346+ seen := make (map [string ][]any , acc .length )
330347 for i := range acc .length {
331348 if err := ctx .Err (); err != nil {
332349 return nil , err
333350 }
334- item1 , err := acc .at (i )
351+ item , err := acc .at (i )
335352 if err != nil {
336353 return nil , fmt .Errorf (`invalid value passed to ArrayValidator: failed to resolve item %d: %w` , i , err )
337354 }
338- for j := i + 1 ; j < acc . length ; j ++ {
339- item2 , err := acc . at ( j )
340- if err != nil {
341- return nil , fmt . Errorf ( `invalid value passed to ArrayValidator: failed to resolve item %d: %w` , j , err )
342- }
343- if reflect .DeepEqual (item1 , item2 ) {
355+ key := unmarshalableKey
356+ if b , err := json . Marshal ( item ); err == nil {
357+ key = string ( b )
358+ }
359+ for _ , prev := range seen [ key ] {
360+ if reflect .DeepEqual (prev , item ) {
344361 return nil , fmt .Errorf (`invalid value passed to ArrayValidator: duplicate items found, uniqueItems violation` )
345362 }
346363 }
364+ seen [key ] = append (seen [key ], item )
347365 }
348366 }
349367
0 commit comments