wstl1/mapping_engine/util/jsonutil/jsonutil.go (703 lines of code) (raw):

// Copyright 2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package jsonutil provides utilities for interacting with a JSON objects. package jsonutil import ( "encoding/binary" "encoding/json" "errors" "fmt" "hash" "hash/fnv" "math" "sort" "strconv" "strings" "unicode" "github.com/google/go-cmp/cmp" /* copybara-comment: cmp */ ) // MarshalJSON serializes the given token into JSON string. func MarshalJSON(tkn JSONToken) string { switch t := tkn.(type) { case JSONNum: // Format is based on RFC 7159. return strconv.FormatFloat(float64(t), 'G', -1, 64) case JSONStr: return fmt.Sprintf("%q", t) case JSONBool: return fmt.Sprintf("%t", t) case nil: return "null" case JSONContainer: return t.String() case JSONArr: return t.String() } return fmt.Sprintf("%v", tkn) } // UnmarshalJSON determines the type of the RawMessage and unmarshals it into a JSONToken. func UnmarshalJSON(in json.RawMessage) (JSONToken, error) { ins := strings.TrimSpace(string(in)) if len(ins) == 0 { return nil, nil } switch { case ins[0] == '{': var jc JSONContainer err := jc.UnmarshalJSON(in) return jc, err case ins[0] == '[': var jc JSONArr err := jc.UnmarshalJSON(in) return jc, err case ins[0] == '"': return JSONStr(strings.Trim(string(ins), `"`)), nil case ins == "true": return JSONBool(true), nil case ins == "false": return JSONBool(false), nil case ins == "null": return JSONToken(nil), nil default: // The only valid choice left is a number. num, err := strconv.ParseFloat(ins, 64) if err != nil { return nil, err } return JSONNum(num), nil } } // IsIndex returns true iff the given field looks like [x] where x is any string of any length. func IsIndex(field string) bool { return len(field) >= 2 && field[0] == '[' && field[len(field)-1] == ']' } // SegmentPath splits the given JSON path into segments/components. Static path components (like // foo.bar.baz are returned verbatim ["foo", "bar", "baz"], and boxed array indices are returned // boxed like foo[123].baz => ["foo", "[123]", "baz"]. A numeric static component like foo.3.bar // will be returned as a string like ["foo", "3", "bar"]. func SegmentPath(path string) ([]string, error) { segs := make([]string, 0, 1) sb := strings.Builder{} var escaped, prevEscaped bool for i, c := range path { escaped = i > 0 && path[i-1] == '\\' && !prevEscaped delim := c == '.' && !escaped // Validation if !unicode.IsLetter(c) && !unicode.IsDigit(c) && !strings.Contains(`-*[]._\' `, string(c)) && !escaped { return nil, fmt.Errorf("invalid character %q", string(c)) } if i > 0 && c == '.' && path[i-1] == '.' && !prevEscaped { return nil, fmt.Errorf("consecutive dots in path %s", path) } if (!delim && c != '\\') || escaped { sb.WriteRune(c) } if (delim || (i < len(path)-1 && path[i+1] == '[')) && sb.Len() > 0 { segs = append(segs, sb.String()) sb.Reset() } prevEscaped = escaped } if sb.Len() > 0 { segs = append(segs, sb.String()) } return segs, nil } // JoinPath rejoins the given path segments into a JSON Path. func JoinPath(segs ...string) string { sb := strings.Builder{} for _, s := range segs { st := strings.Trim(s, " .") if st == "" { continue } if sb.Len() > 0 && !strings.HasSuffix(sb.String(), ".") && !strings.HasPrefix(st, "[") { sb.WriteString(".") } sb.WriteString(st) } return sb.String() } func hasArrayStar(segments []string) bool { for _, seg := range segments { if seg == "[*]" { return true } } return false } // JSONTokenAccessor defines an interface for accessing JSONToken with different engines. type JSONTokenAccessor interface { GetField(src JSONToken, field string) (JSONToken, error) SetField(src JSONToken, field string, dest *JSONToken, forceOverwrite bool, matchNesting bool) error setFieldSegmented(src JSONToken, segments []string, dest *JSONToken, overwrite bool, matchNesting bool) error } // DefaultAccessor is a default JSONTokenAccessor to read/write JSONToken and used by the engine Whistler. type DefaultAccessor struct{} // GetField gets the specified field value for the provided JSON object. // Nested fields can be accessed using the "." notation and repeated fields can be accessed using // the "[i]" notation. E.g. name[0].first // If the field is not found, nil is returned. // Please note that field string should not have "[]" suffix. // For example, // GetField({"foo": "bar"}, "") => {"foo": "bar"} // GetField(123, "") => 123 // GetField({"foo": "bar"}, "foo") => "bar" // GetField({"foo": ["bar", 1]}, "foo") => ["bar", 1] // GetField({"foo": ["bar", 1]}, "foo[0]") => "bar" // GetField({"foo": [{"bar": 1}, {"bar": 2}], "foo[*].bar") => [1, 2] // GetField is a wrapper for DefaultAccessor.GetField(). func GetField(src JSONToken, field string) (JSONToken, error) { a := DefaultAccessor{} return a.GetField(src, field) } // GetFieldSegmented is a wrapper for DefaultAccessor.GetFieldSegmented(). func GetFieldSegmented(src JSONToken, segments []string) (JSONToken, error) { a := DefaultAccessor{} return a.getFieldSegmented(src, segments) } // GetField gets the specified field value for the provided JSON object. func (w DefaultAccessor) GetField(src JSONToken, field string) (JSONToken, error) { segs, err := SegmentPath(field) if err != nil { return nil, fmt.Errorf("failed to segment path: %v", err) } return w.getFieldSegmented(src, segs) } // getFieldSegmented gets the specified field value for the provided JSON object. // segments are path segments like ["foo", "bar", "array", "[2]", "value"]. func (w DefaultAccessor) getFieldSegmented(src JSONToken, segments []string) (JSONToken, error) { if len(segments) == 0 { return src, nil } if src == nil { return nil, nil } seg := segments[0] switch o := src.(type) { case JSONArr: if seg == "[]" { return nil, fmt.Errorf("expected an array index with brackets like [123] but got []") } if !IsIndex(seg) { return nil, fmt.Errorf("expected an array index with brackets like [123] but got %s", seg) } idxSubstr := seg[1 : len(seg)-1] if idxSubstr == "*" { flatten := JSONArr{} for i := range o { f, err := w.getFieldSegmented(o[i], segments[1:]) if err != nil { return nil, fmt.Errorf("error expanding [*] on item index %d: %v", i, err) } // If an array expansion occurs down the line, we need to unnest the resulting array here. if f != nil && hasArrayStar(segments[1:]) { fArr, ok := f.(JSONArr) if !ok { return nil, fmt.Errorf("this is an internal bug: found nested [*] but value was not an array (was %T)", f) } flatten = append(flatten, fArr...) } else { flatten = append(flatten, f) } } return flatten, nil } idx, err := strconv.Atoi(idxSubstr) if err != nil { return nil, fmt.Errorf("could not parse array index %s: %v", seg, err) } if idx < 0 { return nil, fmt.Errorf("negative array indices are not supported but got %d", idx) } if idx >= len(o) { // TODO(): Consider returning a different value for fields that don't exist vs // fields that are actually set to null. return nil, nil } return w.getFieldSegmented(o[idx], segments[1:]) case JSONContainer: if seg == "" || seg == "." { return w.getFieldSegmented(o, segments[1:]) } if item, ok := o[seg]; ok { return w.getFieldSegmented(*item, segments[1:]) } // TODO(): Consider returning a different value for fields that don't exist vs // fields that are actually set to null. return nil, nil case JSONNum, JSONStr, JSONBool: return nil, fmt.Errorf("attempt to key into primitive with key %s", seg) } return nil, fmt.Errorf("this is an internal bug: JSON contained unknown data type %T at %s", src, seg) } // GetArray gets the specified array value for the provided JSON object. // Nested fields can be accessed using the "." notation and repeated fields can be accessed using // the "[i]" notation. E.g. name[0].first func GetArray(j JSONToken, path string) ([]JSONToken, error) { tok, err := GetField(j, path) if err != nil { return nil, err } arr, ok := tok.(JSONArr) if !ok { return nil, fmt.Errorf("expected array but found %T at path: %s", tok, path) } return []JSONToken(arr), nil } // GetString gets the specified field value for the provided JSON object as a string. // Nested fields can be accessed using the "." notation and repeated fields can be accessed using // the "[i]" notation. E.g. name[0].first func GetString(j JSONToken, path string) (string, error) { tok, err := GetField(j, path) if err != nil { return "", err } str, ok := tok.(JSONStr) if !ok { return "", fmt.Errorf("expected string but found %T at path: %s", tok, path) } return string(str), nil } // GetStringOrDefault gets the specified field value for the provided JSON object as a string. // If no object is found at the specified path, the given default string is returned. // Nested fields can be accessed using the "." notation and repeated fields can be accessed using // the "[i]" notation. E.g. name[0].first func GetStringOrDefault(j JSONToken, path, def string) (string, error) { tok, err := GetField(j, path) if err != nil { return "", err } if tok == nil { return def, nil } str, ok := tok.(JSONStr) if !ok { return "", fmt.Errorf("expected string but found %T at path: %s", tok, path) } return string(str), nil } // HasField determines if the specified field exists and is non-nil for the provided JSON object. // Nested fields can be accessed using the "." notation and repeated fields can be accessed using // the "[i]" notation. E.g. name[0].first func HasField(j JSONToken, path string) (bool, error) { obj, err := GetField(j, path) if err != nil { return false, err } return obj != nil, err } // SetField sets the specified field of dest JSONToken into the src provided. // Nested fields can be accessed using the "." notation and repeated fields can be accessed using // the "[i]" notation. E.g. name[0].first. // dest can be of primitive, array or object. // if matchNesting is set to True, each element in src will be unpacked at each level and pass to the corresponding field. // For example, // SetField(0, "foo.baz", &{"foo": {"bar": 1}}, false, false) => {"foo": {"bar": 1, "baz": 0}} // SetField(0, "foo.bar", &{"foo": {"bar": 1}}, true, false) => {"foo": {"bar": 0}} // SetField(1, "foo[]", &{"foo": [0]}, false, false) => {"foo": [0, 1]} // SetField(1, "foo[]", &{"foo": [0]}, false, false) => {"foo": [0, 1]} // SetField{[1, 2], "foo[].bar", &{"foo": [0]}, false, false) => {"foo": [0, {"bar": [1, 2]}]} // SetField{[1, 2], "foo[].bar", &{"foo": [0]}, false, true) => {"foo": [0, {"bar": 1}, {"bar": 2}]} func SetField(src JSONToken, field string, dest *JSONToken, overwrite bool, matchNesting bool) error { w := DefaultAccessor{} return w.SetField(src, field, dest, overwrite, matchNesting) } // SetField sets the specified field value for the provided JSON object. func (w DefaultAccessor) SetField(src JSONToken, field string, dest *JSONToken, overwrite bool, matchNesting bool) error { segments, err := SegmentPath(field) if err != nil { return fmt.Errorf("failed to segment path: %v", err) } return w.setFieldSegmented(src, segments, dest, overwrite, matchNesting) } // setFieldSegmented sets the specified field value for the provided JSON object. // segments are path segments like ["foo", "bar", "array", "[2]", "value"]. func (w DefaultAccessor) setFieldSegmented(src JSONToken, segments []string, dest *JSONToken, overwrite bool, matchNesting bool) error { if len(segments) == 0 { if overwrite { *dest = src return nil } return Merge(src, dest, true /* failOnOverwrite */, false /* overwriteArrays */) } seg := segments[0] if *dest == nil { if IsIndex(seg) { *dest = make(JSONArr, 0, 1) } else { *dest = make(JSONContainer) } } switch o := (*dest).(type) { case JSONArr: return setArrField(w, src, segments, dest, overwrite, matchNesting) case JSONContainer: if seg == "" || seg == "." { var obj JSONToken = o return w.setFieldSegmented(src, segments[1:], &obj, overwrite, matchNesting) } item, ok := o[seg] if !ok { n := JSONToken(nil) item = &n o[seg] = item } return w.setFieldSegmented(src, segments[1:], item, overwrite, matchNesting) case JSONNum, JSONStr, JSONBool: return fmt.Errorf("attempt to key into primitive with key %s", seg) } return fmt.Errorf("this is an internal bug: JSON contained unknown data type %T at %s", *dest, seg) } // setArrField sets the specified field(s) in *dest to data in src using the // Accessor acc (either in parallel or sequentially) when *dest is a JSONArr. // the field(s) to set values are determined by path segments like like ["foo", // "bar", "array", "[2]", "value", "[]"]. when overwrite is set to true, the // field will be overwritten by the new data when matchNesting is set to true, // everytime the segment is '[]', each element in src array will be unpacked and // appended to the dest array as a separate container i.e. dest[].field: src[] // is equivalant to for s in src {dest[].field : s} When there are more // appending operation in dest than the src, the src will be expanded until the // deepest layer of the target // E.g. // setField([1,2], ['A', '[]', 'B', '[]', 'C'], &{"A": [0]}, false, true} => {"A": [0, {"B": [{"C": 1}, {"C": 2}]}]} // setField([[1, 2],[[3]], 4], ['A', '[]', 'B', '[]', 'C'], &{"A": [0]}, false, true} => {"A": [0, {"B": [{"C": 1}, {"C": 2}]}, {"B": [{"C": [3]}]}, {"B": [{"C": 4}]}]} func setArrField(acc JSONTokenAccessor, src JSONToken, segments []string, dest *JSONToken, overwrite bool, matchNesting bool) error { o := (*dest).(JSONArr) seg := segments[0] if seg == "[]" { // If both of src and dest are arrays, src will be appended into dest. if a, isArr := src.(JSONArr); isArr { if len(segments) == 1 { *dest = append(o, a...) return nil } else if matchNesting { for _, element := range a { n := JSONToken(nil) item := &n if err := acc.setFieldSegmented(element, segments[1:], item, overwrite, matchNesting); err != nil { return err } *dest = append((*dest).(JSONArr), n) } return nil } } seg = fmt.Sprintf("[%d]", len(o)) } if !IsIndex(seg) { return fmt.Errorf("expected an array index with brackets like [123] but got %s", seg) } idxSubstr := seg[1 : len(seg)-1] if idxSubstr == "*" { return fmt.Errorf("cannot use [*] when writing to a field (can only use it when reading)") } idx, err := strconv.Atoi(idxSubstr) if err != nil { return fmt.Errorf("could not parse array index %s: %v", seg, err) } if idx < 0 { return fmt.Errorf("negative array indices are not supported but got %d", idx) } if idx >= len(o) { o = append(o, make(JSONArr, idx-len(o)+1)...) *dest = o } return acc.setFieldSegmented(src, segments[1:], &o[idx], overwrite, matchNesting) } // Merge merges two JSONTokens together. If failOnOverwrite is true, this method guarantees that no // existing data anywhere in the destination will be lost (unless overwriteArrays is true). func Merge(src JSONToken, dest *JSONToken, failOnOverwrite, overwriteArrays bool) error { if dest == nil { return errors.New("destination is nil pointer") } else if *dest == nil { *dest = src return nil } // Overwrite or fail. if isPrim(src) { if err := getOverwriteError(dest); failOnOverwrite && err != nil { return err } *dest = src return nil } switch d := (*dest).(type) { case JSONContainer: if srcCon, ok := src.(JSONContainer); ok { for k, v := range srcCon { if d[k] == nil { d[k] = v } else if err := Merge(*v, d[k], failOnOverwrite, overwriteArrays); err != nil { return err } } } else if len(d) == 0 { // If the destination is empty, allow type change. *dest = src } else { return fmt.Errorf("can't merge source %T with destination %T", src, d) } case JSONArr: if srcArr, ok := src.(JSONArr); ok { if !overwriteArrays { *dest = append(d, srcArr...) } else { *dest = srcArr } } else if len(d) == 0 { // If the destination is empty, allow type change. *dest = src } else { // TODO(): Append src as is to dest? return fmt.Errorf("can't merge source %T with destination %T", src, d) } default: return fmt.Errorf("this is an internal bug: destination is of unknown type %T", d) } return nil } // getOverwriteError returns the problem with attempting to overwrite the given destination token. // If the destination can be safely overwritten (no data lost), returns nil. func getOverwriteError(dest *JSONToken) error { if *dest != nil && isPrim(*dest) { return fmt.Errorf("attempt to overwrite primitive destination with primitive source") } if jc, ok := (*dest).(JSONContainer); ok && len(jc) > 0 { return fmt.Errorf("attempt to overwrite non-empty container destination with primitive source") } if ja, ok := (*dest).(JSONArr); ok && len(ja) > 0 { return fmt.Errorf("attempt to overwrite non-empty array destination with primitive source") } return nil } func isPrim(object JSONToken) bool { switch (object).(type) { case JSONNum: return true case JSONStr: return true case JSONBool: return true case nil: return true } return false } // JSONToken represents a base JSON token. It can be a primitive, an array, // or a container object. type JSONToken interface { jsonObject() Value() JSONToken Equal(JSONToken) bool } // GetValue gets actual JSON value in a JSONToken. If it is nil, return nil. func GetValue(j JSONToken) JSONToken { if j == nil { return nil } return j.Value() } // JSONContainer is a JSON object that contains some child fields. For example, // { "foo": "bar", "baz": [1, 2, 3] } type JSONContainer map[string]*JSONToken // JSONArr is a JSON array that contains 0 or more JSONObjects. type JSONArr []JSONToken // JSONStr represents a primitive JSON string. type JSONStr string // JSONNum represents a primitive JSON float. type JSONNum float64 // JSONBool represents a primitive JSON bool. type JSONBool bool func (JSONContainer) jsonObject() {} func (JSONArr) jsonObject() {} func (JSONStr) jsonObject() {} func (JSONNum) jsonObject() {} func (JSONBool) jsonObject() {} // Value gets a JSON object. func (c JSONContainer) Value() JSONToken { return c } // Value gets a JSON array. func (a JSONArr) Value() JSONToken { return a } // Value gets a primitive JSON string. func (s JSONStr) Value() JSONToken { return s } // Value gets a primitive JSON float. func (n JSONNum) Value() JSONToken { return n } // Value gets a primitive JSON bool. func (b JSONBool) Value() JSONToken { return b } // Equal reports whether j and itself are equal by recursively checking each element in the map. func (c JSONContainer) Equal(j JSONToken) bool { jc, ok := j.(JSONContainer) if !ok || len(c) != len(jc) { return false } for k, x := range c { y, ok := jc[k] if !ok { return false } if *x == nil { if *y != nil { return false } } else if !(*x).Equal(*y) { return false } } return true } // Equal reports whether j and itself are equal by recursively checking each element in the array. func (a JSONArr) Equal(j JSONToken) bool { ja, ok := j.(JSONArr) if !ok || len(a) != len(ja) { return false } for i, x := range a { if x == nil { if ja[i] != nil { return false } } else if !x.Equal(ja[i]) { return false } } return true } // Equal reports whether j and itself are equal. func (s JSONStr) Equal(j JSONToken) bool { js, ok := j.(JSONStr) return ok && s == js } // Equal reports whether j and itself are equal. func (n JSONNum) Equal(j JSONToken) bool { jn, ok := j.(JSONNum) return ok && n == jn } // Equal reports whether j and itself are equal. func (b JSONBool) Equal(j JSONToken) bool { jb, ok := j.(JSONBool) return ok && b == jb } func (c JSONContainer) String() string { o := make([]string, 0, len(c)) for k, v := range c { o = append(o, fmt.Sprintf("%q:%s", k, MarshalJSON(*v))) } return fmt.Sprintf("{%s}", strings.Join(o, ",")) } func (a JSONArr) String() string { o := make([]string, 0, len(a)) for _, v := range a { o = append(o, MarshalJSON(v)) } return fmt.Sprintf("[%s]", strings.Join(o, ",")) } // JSONPrimitive represents one of the JSON primitive types (Str, Bool, Num). This is useful for // defining a variable/parameter that is guaranteed to be a leaf node in the JSON tree. type JSONPrimitive interface { jsonPrimitive() } func (JSONStr) jsonPrimitive() {} func (JSONBool) jsonPrimitive() {} func (JSONNum) jsonPrimitive() {} // unmarshaledToJSONToken wraps the output of json.Unmarshal() into one of the above JSON wrapper // types. For arrays and containers will recursively convert their contents. unmarshaledToJSONToken // exclusively supports int, float64, bool, string, []interface{}, and map[string]interface{} // (where interface{} is a type supported by unmarshaledToJSONToken). func unmarshaledToJSONToken(object interface{}) (JSONToken, error) { if object == nil { return nil, nil } var token JSONToken switch o := object.(type) { case int: n := JSONNum(o) token = n case float64: n := JSONNum(o) token = n case bool: b := JSONBool(o) token = b case string: s := JSONStr(o) token = s case []interface{}: ja := make(JSONArr, 0, len(o)) for _, obj := range o { co, err := unmarshaledToJSONToken(obj) if err != nil { return nil, err } ja = append(ja, co) } token = ja case map[string]interface{}: jc := make(JSONContainer) for k, v := range o { cv, err := unmarshaledToJSONToken(v) if err != nil { return nil, err } jc[k] = &cv } token = jc default: return nil, fmt.Errorf("unable to wrap a %T in JSON wrapper types", o) } return token, nil } // UnmarshalJSON sets the contents of the receiver to be the unmarshaled and converted contents of // the given JSON (hence given JSON must be of an object, rather than primitive or array). func (c *JSONContainer) UnmarshalJSON(j json.RawMessage) error { var m map[string]interface{} err := json.Unmarshal(j, &m) if err != nil { return err } cm, err := unmarshaledToJSONToken(m) if err != nil { return err } *c = cm.(JSONContainer) return nil } // UnmarshalJSON sets the contents of the receiver to be the unmarshaled and converted contents of // the given JSON (hence given JSON must be of an array, rather than primitive or object). func (a *JSONArr) UnmarshalJSON(j json.RawMessage) error { var m []interface{} err := json.Unmarshal(j, &m) if err != nil { return err } cm, err := unmarshaledToJSONToken(m) if err != nil { return err } *a = cm.(JSONArr) return nil } // UnmarshalRawMessages unmarshals an array of raw json messages into an array of JSON containers. func UnmarshalRawMessages(resources []*json.RawMessage) (JSONArr, error) { array := make(JSONArr, 0, len(resources)) for _, raw := range resources { jc := &JSONContainer{} if err := jc.UnmarshalJSON(*raw); err != nil { return nil, fmt.Errorf("error parsing retrieved resources %s", err) } array = append(array, *jc) } return array, nil } // Deepcopy will make a deep copy of the elements inside a JSONToken func Deepcopy(t JSONToken) JSONToken { switch c := t.(type) { case JSONContainer: a := make(JSONContainer) for k, v := range c { copied := Deepcopy(*v) a[k] = &copied } return a case JSONArr: a := make(JSONArr, 0, len(c)) for _, v := range c { a = append(a, Deepcopy(v)) } return a default: return c } } // UnorderedEqual recursively compares two given tokens. // Both of key order and array item order are not considered. func UnorderedEqual(x, y JSONToken) bool { hx, err := Hash(x, true) if err != nil { return false } hy, err := Hash(y, true) if err != nil { return false } return cmp.Equal(hx, hy) } // Hash converts the given token into a hash. Key order is not considered. // This is not cryptographically secure, and is not to be used for secure hashing. // If arrayWithoutOrder is true, array item order will be not considered. func Hash(obj JSONToken, arrayWithoutOrder bool) ([]byte, error) { h := fnv.New128() err := hashObj(obj, h, arrayWithoutOrder) return h.Sum([]byte{}), err } func hashObj(obj JSONToken, h hash.Hash, arrayWithoutOrder bool) error { switch t := obj.(type) { case JSONStr: return hashBytes("str", []byte(t), h) case JSONNum: var b [8]byte binary.BigEndian.PutUint64(b[:], math.Float64bits(float64(t))) return hashBytes("num", b[:], h) case JSONBool: b := []byte{1} if t { b = []byte{2} } return hashBytes("bool", b, h) case JSONArr: if arrayWithoutOrder { hc := make([]byte, 16) for _, a := range t { ah := fnv.New128() if err := hashObj(a, ah, arrayWithoutOrder); err != nil { return err } if err := xor128(hc, ah.Sum(nil)); err != nil { return err } } return hashBytes("arr", hc, h) } for _, a := range t { if err := hashObj(a, h, arrayWithoutOrder); err != nil { return err } } case JSONContainer: var kv []struct { k string v JSONToken } for k, v := range t { kv = append(kv, struct { k string v JSONToken }{k, *v}) } // Stable sort not needed because container can't have multiple occurrences of the same key. sort.Slice(kv, func(i, j int) bool { return kv[i].k < kv[j].k }) for _, a := range kv { if err := hashBytes("key", []byte(a.k), h); err != nil { return err } if err := hashObj(a.v, h, arrayWithoutOrder); err != nil { return err } } case nil: return hashBytes("nil", []byte{}, h) default: return fmt.Errorf("this is an internal bug: unknown JSON type %T", obj) } return nil } func xor128(x []byte, y []byte) error { if len(x) != 16 || len(y) != 16 { return fmt.Errorf("this is an internal bug: xor128() got data that was not 128 bits long: %v, %v", x, y) } for i := 0; i < 16; i++ { x[i] ^= y[i] } return nil } // StringToToken returns a pointer to a JSONToken created using the input string. func StringToToken(text string) *JSONToken { t := JSONToken(JSONStr(text)) return &t } func hashBytes(salt string, data []byte, h hash.Hash) error { sb := append([]byte(salt), data...) n, err := h.Write(sb) if err != nil { return err } if n != len(sb) { return fmt.Errorf("this is an internal bug: could not write %d bytes to %T (only wrote %d): %v", len(sb), h, n, err) } return nil }