@@ -6,11 +6,15 @@ package jsonschema
6
6
7
7
import (
8
8
"bytes"
9
+ "cmp"
10
+ "encoding/binary"
9
11
"encoding/json"
10
12
"fmt"
13
+ "hash/maphash"
11
14
"math"
12
15
"math/big"
13
16
"reflect"
17
+ "slices"
14
18
)
15
19
16
20
// Equal reports whether two Go values representing JSON values are equal according
@@ -126,14 +130,93 @@ func equalValue(x, y reflect.Value) bool {
126
130
return x .String () == y .String ()
127
131
case reflect .Bool :
128
132
return x .Bool () == y .Bool ()
129
- case reflect .Complex64 , reflect .Complex128 :
130
- return x .Complex () == y .Complex ()
131
133
// Ints, uints and floats handled in jsonNumber, at top of function.
132
134
default :
133
135
panic (fmt .Sprintf ("unsupported kind: %s" , x .Kind ()))
134
136
}
135
137
}
136
138
139
+ // hashValue adds v to the data hashed by h. v must not have cycles.
140
+ // hashValue panics if the value contains functions or channels, or maps whose
141
+ // key type is not string.
142
+ // It ignores unexported fields of structs.
143
+ // Calls to hashValue with the equal values (in the sense
144
+ // of [Equal]) result in the same sequence of values written to the hash.
145
+ func hashValue (h * maphash.Hash , v reflect.Value ) {
146
+ // TODO: replace writes of basic types with WriteComparable in 1.24.
147
+
148
+ writeUint := func (u uint64 ) {
149
+ var buf [8 ]byte
150
+ binary .BigEndian .PutUint64 (buf [:], u )
151
+ h .Write (buf [:])
152
+ }
153
+
154
+ var write func (reflect.Value )
155
+ write = func (v reflect.Value ) {
156
+ if r , ok := jsonNumber (v ); ok {
157
+ // We want 1.0 and 1 to hash the same.
158
+ // big.Rats are always normalized, so they will be.
159
+ // We could do this more efficiently by handling the int and float cases
160
+ // separately, but that's premature.
161
+ writeUint (uint64 (r .Sign () + 1 ))
162
+ h .Write (r .Num ().Bytes ())
163
+ h .Write (r .Denom ().Bytes ())
164
+ return
165
+ }
166
+ switch v .Kind () {
167
+ case reflect .Invalid :
168
+ h .WriteByte (0 )
169
+ case reflect .String :
170
+ h .WriteString (v .String ())
171
+ case reflect .Bool :
172
+ if v .Bool () {
173
+ h .WriteByte (1 )
174
+ } else {
175
+ h .WriteByte (0 )
176
+ }
177
+ case reflect .Complex64 , reflect .Complex128 :
178
+ c := v .Complex ()
179
+ writeUint (math .Float64bits (real (c )))
180
+ writeUint (math .Float64bits (imag (c )))
181
+ case reflect .Array , reflect .Slice :
182
+ // Although we could treat []byte more efficiently,
183
+ // JSON values are unlikely to contain them.
184
+ writeUint (uint64 (v .Len ()))
185
+ for i := range v .Len () {
186
+ write (v .Index (i ))
187
+ }
188
+ case reflect .Interface , reflect .Pointer :
189
+ write (v .Elem ())
190
+ case reflect .Struct :
191
+ t := v .Type ()
192
+ for i := range t .NumField () {
193
+ if sf := t .Field (i ); sf .IsExported () {
194
+ write (v .FieldByIndex (sf .Index ))
195
+ }
196
+ }
197
+ case reflect .Map :
198
+ if v .Type ().Key ().Kind () != reflect .String {
199
+ panic ("map with non-string key" )
200
+ }
201
+ // Sort the keys so the hash is deterministic.
202
+ keys := v .MapKeys ()
203
+ // Write the length. That distinguishes between, say, two consecutive
204
+ // maps with disjoint keys from one map that has the items of both.
205
+ writeUint (uint64 (len (keys )))
206
+ slices .SortFunc (keys , func (x , y reflect.Value ) int { return cmp .Compare (x .String (), y .String ()) })
207
+ for _ , k := range keys {
208
+ write (k )
209
+ write (v .MapIndex (k ))
210
+ }
211
+ // Ints, uints and floats handled in jsonNumber, at top of function.
212
+ default :
213
+ panic (fmt .Sprintf ("unsupported kind: %s" , v .Kind ()))
214
+ }
215
+ }
216
+
217
+ write (v )
218
+ }
219
+
137
220
// jsonNumber converts a numeric value or a json.Number to a [big.Rat].
138
221
// If v is not a number, it returns nil, false.
139
222
func jsonNumber (v reflect.Value ) (* big.Rat , bool ) {
0 commit comments