195 lines
4.6 KiB
Go
195 lines
4.6 KiB
Go
// Package jsonvalue 比较 JSON 的实际内容,不将 JSON 数值转换为浮点数。
|
|
package jsonvalue
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// Canonical 生成内部 orbit-json-v1 摘要输入:对象键排序、重复键拒绝、
|
|
// 精确十进制数规范化。该格式不是对外 JSON 编码承诺,也不宣称实现 RFC 8785。
|
|
func Canonical(raw []byte) ([]byte, error) {
|
|
if !utf8.Valid(raw) {
|
|
return nil, errors.New("JSON must be valid UTF-8")
|
|
}
|
|
if err := validEscapes(raw); err != nil {
|
|
return nil, err
|
|
}
|
|
d := json.NewDecoder(bytes.NewReader(raw))
|
|
d.UseNumber()
|
|
b, err := value(d, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := d.Token(); err != io.EOF {
|
|
return nil, errors.New("exactly one JSON value required")
|
|
}
|
|
return b, nil
|
|
}
|
|
func value(d *json.Decoder, depth int) ([]byte, error) {
|
|
if depth > 32 {
|
|
return nil, errors.New("JSON nesting exceeds 32 levels")
|
|
}
|
|
t, err := d.Token()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch v := t.(type) {
|
|
case json.Delim:
|
|
switch v {
|
|
case '{':
|
|
fields := map[string][]byte{}
|
|
keys := []string{}
|
|
for d.More() {
|
|
token, err := d.Token()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
key, ok := token.(string)
|
|
if !ok {
|
|
return nil, errors.New("invalid object key")
|
|
}
|
|
if _, ok := fields[key]; ok {
|
|
return nil, errors.New("duplicate JSON object key")
|
|
}
|
|
b, err := value(d, depth+1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fields[key] = b
|
|
keys = append(keys, key)
|
|
}
|
|
if close, err := d.Token(); err != nil || close != json.Delim('}') {
|
|
return nil, errors.New("unterminated JSON object")
|
|
}
|
|
sort.Strings(keys)
|
|
var b bytes.Buffer
|
|
b.WriteByte('{')
|
|
for i, k := range keys {
|
|
if i > 0 {
|
|
b.WriteByte(',')
|
|
}
|
|
key, _ := json.Marshal(k)
|
|
b.Write(key)
|
|
b.WriteByte(':')
|
|
b.Write(fields[k])
|
|
}
|
|
b.WriteByte('}')
|
|
return b.Bytes(), nil
|
|
case '[':
|
|
var b bytes.Buffer
|
|
b.WriteByte('[')
|
|
n := 0
|
|
for d.More() {
|
|
v, err := value(d, depth+1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if n > 0 {
|
|
b.WriteByte(',')
|
|
}
|
|
b.Write(v)
|
|
n++
|
|
}
|
|
if close, err := d.Token(); err != nil || close != json.Delim(']') {
|
|
return nil, errors.New("unterminated JSON array")
|
|
}
|
|
b.WriteByte(']')
|
|
return b.Bytes(), nil
|
|
default:
|
|
return nil, errors.New("unexpected JSON delimiter")
|
|
}
|
|
case json.Number:
|
|
return number(string(v))
|
|
default:
|
|
return json.Marshal(v)
|
|
}
|
|
}
|
|
func number(s string) ([]byte, error) {
|
|
if len(s) > 256 {
|
|
return nil, errors.New("JSON number exceeds 256 characters")
|
|
}
|
|
// 仅用作有限范围检查;摘要始终由下面的十进制文本运算生成。
|
|
if _, err := strconv.ParseFloat(s, 64); err != nil {
|
|
return nil, errors.New("JSON number outside supported range")
|
|
}
|
|
negative := strings.HasPrefix(s, "-")
|
|
s = strings.TrimPrefix(s, "-")
|
|
exponent := 0
|
|
if i := strings.IndexAny(s, "eE"); i >= 0 {
|
|
var err error
|
|
exponent, err = strconv.Atoi(s[i+1:])
|
|
if err != nil || exponent < -308 || exponent > 308 {
|
|
return nil, errors.New("JSON exponent outside supported range")
|
|
}
|
|
s = s[:i]
|
|
}
|
|
if i := strings.IndexByte(s, '.'); i >= 0 {
|
|
exponent -= len(s) - i - 1
|
|
s = s[:i] + s[i+1:]
|
|
}
|
|
s = strings.TrimLeft(s, "0")
|
|
if s == "" {
|
|
return []byte("0"), nil
|
|
}
|
|
for strings.HasSuffix(s, "0") {
|
|
s = s[:len(s)-1]
|
|
exponent++
|
|
}
|
|
if exponent < -308 || exponent > 308 {
|
|
return nil, errors.New("normalized JSON exponent outside supported range")
|
|
}
|
|
if exponent+len(s)-1 > 308 {
|
|
return nil, errors.New("JSON number outside supported range")
|
|
}
|
|
if negative {
|
|
s = "-" + s
|
|
}
|
|
if exponent != 0 {
|
|
s += "e" + strconv.Itoa(exponent)
|
|
}
|
|
return []byte(s), nil
|
|
}
|
|
|
|
// encoding/json 会替换孤立 UTF-16 代理项;拒绝它们,避免两个不同输入被悄悄合并。
|
|
func validEscapes(raw []byte) error {
|
|
for i := 0; i < len(raw); i++ {
|
|
if raw[i] != '\\' {
|
|
continue
|
|
}
|
|
i++
|
|
if i >= len(raw) || raw[i] != 'u' {
|
|
continue
|
|
}
|
|
if i+4 >= len(raw) {
|
|
return errors.New("invalid unicode escape")
|
|
}
|
|
v, err := strconv.ParseUint(string(raw[i+1:i+5]), 16, 16)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
i += 4
|
|
if v >= 0xdc00 && v <= 0xdfff {
|
|
return errors.New("unpaired unicode surrogate")
|
|
}
|
|
if v < 0xd800 || v > 0xdbff {
|
|
continue
|
|
}
|
|
if i+6 >= len(raw) || raw[i+1] != '\\' || raw[i+2] != 'u' {
|
|
return errors.New("unpaired unicode surrogate")
|
|
}
|
|
v, err = strconv.ParseUint(string(raw[i+3:i+7]), 16, 16)
|
|
if err != nil || v < 0xdc00 || v > 0xdfff {
|
|
return errors.New("unpaired unicode surrogate")
|
|
}
|
|
i += 6
|
|
}
|
|
return nil
|
|
}
|