@@ -6,11 +6,15 @@ package jsonschema
66
77import  (
88	"bytes" 
9+ 	"cmp" 
10+ 	"encoding/binary" 
911	"encoding/json" 
1012	"fmt" 
13+ 	"hash/maphash" 
1114	"math" 
1215	"math/big" 
1316	"reflect" 
17+ 	"slices" 
1418)
1519
1620// Equal reports whether two Go values representing JSON values are equal according 
@@ -126,14 +130,93 @@ func equalValue(x, y reflect.Value) bool {
126130		return  x .String () ==  y .String ()
127131	case  reflect .Bool :
128132		return  x .Bool () ==  y .Bool ()
129- 	case  reflect .Complex64 , reflect .Complex128 :
130- 		return  x .Complex () ==  y .Complex ()
131133	// Ints, uints and floats handled in jsonNumber, at top of function. 
132134	default :
133135		panic (fmt .Sprintf ("unsupported kind: %s" , x .Kind ()))
134136	}
135137}
136138
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+ 
137220// jsonNumber converts a numeric value or a json.Number to a [big.Rat]. 
138221// If v is not a number, it returns nil, false. 
139222func  jsonNumber (v  reflect.Value ) (* big.Rat , bool ) {
0 commit comments