Skip to content

Commit

Permalink
perf: check uniqueItems using map
Browse files Browse the repository at this point in the history
  • Loading branch information
santhosh-tekuri committed Jul 20, 2023
1 parent caf0c40 commit d0f75c8
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
go: [1.15]
go: [1.19]
steps:
- name: setup go
uses: actions/setup-go@v3
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/santhosh-tekuri/jsonschema/v5

go 1.15
go 1.19
81 changes: 75 additions & 6 deletions schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"bytes"
"encoding/json"
"fmt"
"hash/maphash"
"math/big"
"net/url"
"regexp"
"sort"
"strconv"
"strings"
"unicode/utf8"
Expand Down Expand Up @@ -421,14 +423,39 @@ func (s *Schema) validate(scope []schemaRef, vscope int, spath string, v interfa
errors = append(errors, validationError("maxItems", "maximum %d items required, but found %d items", s.MaxItems, len(v)))
}
if s.UniqueItems {
outer:
for i := 1; i < len(v); i++ {
for j := 0; j < i; j++ {
if equals(v[i], v[j]) {
errors = append(errors, validationError("uniqueItems", "items at index %d and %d are equal", j, i))
break outer
if len(v) <= 20 {
outer1:
for i := 1; i < len(v); i++ {
for j := 0; j < i; j++ {
if equals(v[i], v[j]) {
errors = append(errors, validationError("uniqueItems", "items at index %d and %d are equal", j, i))
break outer1
}
}
}
} else {
m := make(map[uint64][]int)
var h maphash.Hash
outer2:
for i, item := range v {
h.Reset()
hash(item, &h)
k := h.Sum64()
if err != nil {
panic(err)
}
arr, ok := m[k]
if ok {
for _, j := range arr {
if equals(v[j], item) {
errors = append(errors, validationError("uniqueItems", "items at index %d and %d are equal", j, i))
break outer2
}
}
}
arr = append(arr, i)
m[k] = arr
}
}
}

Expand Down Expand Up @@ -823,6 +850,48 @@ func equals(v1, v2 interface{}) bool {
}
}

func hash(v interface{}, h *maphash.Hash) {
switch v := v.(type) {
case nil:
h.WriteByte(0)
case bool:
h.WriteByte(1)
if v {
h.WriteByte(1)
} else {
h.WriteByte(0)
}
case json.Number, float32, float64, int, int8, int32, int64, uint, uint8, uint32, uint64:
h.WriteByte(2)
num, _ := new(big.Rat).SetString(fmt.Sprint(v))
h.Write(num.Num().Bytes())
h.Write(num.Denom().Bytes())
case string:
h.WriteByte(3)
h.WriteString(v)
case []interface{}:
h.WriteByte(4)
for _, item := range v {
hash(item, h)
}
case map[string]interface{}:
h.WriteByte(5)
props := make([]string, 0, len(v))
for prop := range v {
props = append(props, prop)
}
sort.Slice(props, func(i, j int) bool {
return props[i] < props[j]
})
for _, prop := range props {
hash(prop, h)
hash(v[prop], h)
}
default:
panic(InvalidJSONTypeError(fmt.Sprintf("%T", v)))
}
}

// escape converts given token to valid json-pointer token
func escape(token string) string {
token = strings.ReplaceAll(token, "~", "~0")
Expand Down

0 comments on commit d0f75c8

Please sign in to comment.