mirror of https://github.com/tidwall/gjson.git
Remove encoding/json dependency
The only purpose of using the built-in Go was to encode json strings that had unicode or needed to escaped. This commit adds the new function `AppendJSONString` which allows for appending strings as their json representation to a byte slice. It's about 2x faster than using json.Marshal.
This commit is contained in:
parent
56c0a0aa5b
commit
c3bb2c39ba
73
gjson.go
73
gjson.go
|
@ -2,7 +2,6 @@
|
||||||
package gjson
|
package gjson
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -1824,17 +1823,64 @@ func isSimpleName(component string) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendJSONString(dst []byte, s string) []byte {
|
var hexchars = [...]byte{
|
||||||
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||||
|
'a', 'b', 'c', 'd', 'e', 'f',
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendHex16(dst []byte, x uint16) []byte {
|
||||||
|
return append(dst,
|
||||||
|
hexchars[x>>12&0xF], hexchars[x>>8&0xF],
|
||||||
|
hexchars[x>>4&0xF], hexchars[x>>0&0xF],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendJSONString is a convenience function that converts the provided string
|
||||||
|
// to a valid JSON string and appends it to dst.
|
||||||
|
func AppendJSONString(dst []byte, s string) []byte {
|
||||||
|
dst = append(dst, make([]byte, len(s)+2)...)
|
||||||
|
dst = append(dst[:len(dst)-len(s)-2], '"')
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
if s[i] < ' ' || s[i] == '\\' || s[i] == '"' || s[i] > 126 {
|
if s[i] < ' ' {
|
||||||
d, _ := json.Marshal(s)
|
dst = append(dst, '\\')
|
||||||
return append(dst, string(d)...)
|
switch s[i] {
|
||||||
|
case '\n':
|
||||||
|
dst = append(dst, 'n')
|
||||||
|
case '\r':
|
||||||
|
dst = append(dst, 'r')
|
||||||
|
case '\t':
|
||||||
|
dst = append(dst, 't')
|
||||||
|
default:
|
||||||
|
dst = append(dst, 'u')
|
||||||
|
dst = appendHex16(dst, uint16(s[i]))
|
||||||
|
}
|
||||||
|
} else if s[i] == '>' || s[i] == '<' || s[i] == '&' {
|
||||||
|
dst = append(dst, '\\', 'u')
|
||||||
|
dst = appendHex16(dst, uint16(s[i]))
|
||||||
|
} else if s[i] == '\\' {
|
||||||
|
dst = append(dst, '\\', '\\')
|
||||||
|
} else if s[i] == '"' {
|
||||||
|
dst = append(dst, '\\', '"')
|
||||||
|
} else if s[i] > 127 {
|
||||||
|
// read utf8 character
|
||||||
|
r, n := utf8.DecodeRuneInString(s[i:])
|
||||||
|
if n == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if r == utf8.RuneError && n == 1 {
|
||||||
|
dst = append(dst, `\ufffd`...)
|
||||||
|
} else if r == '\u2028' || r == '\u2029' {
|
||||||
|
dst = append(dst, `\u202`...)
|
||||||
|
dst = append(dst, hexchars[r&0xF])
|
||||||
|
} else {
|
||||||
|
dst = append(dst, s[i:i+n]...)
|
||||||
|
}
|
||||||
|
i = i + n - 1
|
||||||
|
} else {
|
||||||
|
dst = append(dst, s[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dst = append(dst, '"')
|
return append(dst, '"')
|
||||||
dst = append(dst, s...)
|
|
||||||
dst = append(dst, '"')
|
|
||||||
return dst
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type parseContext struct {
|
type parseContext struct {
|
||||||
|
@ -1924,14 +1970,14 @@ func Get(json, path string) Result {
|
||||||
if sub.name[0] == '"' && Valid(sub.name) {
|
if sub.name[0] == '"' && Valid(sub.name) {
|
||||||
b = append(b, sub.name...)
|
b = append(b, sub.name...)
|
||||||
} else {
|
} else {
|
||||||
b = appendJSONString(b, sub.name)
|
b = AppendJSONString(b, sub.name)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
last := nameOfLast(sub.path)
|
last := nameOfLast(sub.path)
|
||||||
if isSimpleName(last) {
|
if isSimpleName(last) {
|
||||||
b = appendJSONString(b, last)
|
b = AppendJSONString(b, last)
|
||||||
} else {
|
} else {
|
||||||
b = appendJSONString(b, "_")
|
b = AppendJSONString(b, "_")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b = append(b, ':')
|
b = append(b, ':')
|
||||||
|
@ -2974,8 +3020,7 @@ func modFromStr(json, arg string) string {
|
||||||
// @tostr converts a string to json
|
// @tostr converts a string to json
|
||||||
// {"id":1023,"name":"alert"} -> "{\"id\":1023,\"name\":\"alert\"}"
|
// {"id":1023,"name":"alert"} -> "{\"id\":1023,\"name\":\"alert\"}"
|
||||||
func modToStr(str, arg string) string {
|
func modToStr(str, arg string) string {
|
||||||
data, _ := json.Marshal(str)
|
return string(AppendJSONString(nil, str))
|
||||||
return string(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func modGroup(json, arg string) string {
|
func modGroup(json, arg string) string {
|
||||||
|
|
|
@ -2503,3 +2503,43 @@ func TestGroup(t *testing.T) {
|
||||||
res = Get(json, `{"id":issues.#.id,"plans":issues.#.fields.labels.#(%"plan:*")#|#.#}|@group|#(plans>=2)#.id`).Raw
|
res = Get(json, `{"id":issues.#.id,"plans":issues.#.fields.labels.#(%"plan:*")#|#.#}|@group|#(plans>=2)#.id`).Raw
|
||||||
assert(t, res == `["123"]`)
|
assert(t, res == `["123"]`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testJSONString(t *testing.T, str string) {
|
||||||
|
gjsonString := string(AppendJSONString(nil, str))
|
||||||
|
data, err := json.Marshal(str)
|
||||||
|
if err != nil {
|
||||||
|
panic(123)
|
||||||
|
}
|
||||||
|
goString := string(data)
|
||||||
|
if gjsonString != goString {
|
||||||
|
t.Fatal(strconv.Quote(str) + "\n\t" +
|
||||||
|
gjsonString + "\n\t" +
|
||||||
|
goString + "\n\t<<< MISMATCH >>>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONString(t *testing.T) {
|
||||||
|
testJSONString(t, "hello")
|
||||||
|
testJSONString(t, "he\"llo")
|
||||||
|
testJSONString(t, "he\"l\\lo")
|
||||||
|
const input = `{"utf8":"Example emoji, KO: \ud83d\udd13, \ud83c\udfc3 ` +
|
||||||
|
`OK: \u2764\ufe0f "}`
|
||||||
|
value := Get(input, "utf8")
|
||||||
|
var s string
|
||||||
|
json.Unmarshal([]byte(value.Raw), &s)
|
||||||
|
if value.String() != s {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", s, value.String())
|
||||||
|
}
|
||||||
|
testJSONString(t, s)
|
||||||
|
testJSONString(t, "R\xfd\xfc\a!\x82eO\x16?_\x0f\x9ab\x1dr")
|
||||||
|
testJSONString(t, "_\xb9\v\xad\xb3|X!\xb6\xd9U&\xa4\x1a\x95\x04")
|
||||||
|
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
start := time.Now()
|
||||||
|
var buf [16]byte
|
||||||
|
for time.Since(start) < time.Second*2 {
|
||||||
|
if _, err := rng.Read(buf[:]); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testJSONString(t, string(buf[:]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue