Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e3f6aeaa5 | ||
|
|
f92dbfc6b2 | ||
|
|
ba784d767a | ||
|
|
f123b34087 | ||
|
|
afaeb95620 | ||
|
|
3cd3a11923 | ||
|
|
3a977634eb | ||
|
|
9ed3f8e1a5 | ||
|
|
93d61e6369 |
110
gjson.go
110
gjson.go
@@ -13,7 +13,6 @@ import (
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tidwall/match"
|
||||
)
|
||||
@@ -78,7 +77,20 @@ func (t Result) String() string {
|
||||
case False:
|
||||
return "false"
|
||||
case Number:
|
||||
return strconv.FormatFloat(t.Num, 'f', -1, 64)
|
||||
if len(t.Raw) == 0 {
|
||||
// calculated result
|
||||
return strconv.FormatFloat(t.Num, 'f', -1, 64)
|
||||
}
|
||||
var i int
|
||||
if t.Raw[0] == '-' {
|
||||
i++
|
||||
}
|
||||
for ; i < len(t.Raw); i++ {
|
||||
if t.Raw[i] < '0' || t.Raw[i] > '9' {
|
||||
return strconv.FormatFloat(t.Num, 'f', -1, 64)
|
||||
}
|
||||
}
|
||||
return t.Raw
|
||||
case String:
|
||||
return t.Str
|
||||
case JSON:
|
||||
@@ -345,24 +357,30 @@ func (t Result) arrayOrMap(vc byte, valueize bool) (r arrayOrMapResult) {
|
||||
if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' {
|
||||
value.Type = Number
|
||||
value.Raw, value.Num = tonum(json[i:])
|
||||
value.Str = ""
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
case '{', '[':
|
||||
value.Type = JSON
|
||||
value.Raw = squash(json[i:])
|
||||
value.Str, value.Num = "", 0
|
||||
case 'n':
|
||||
value.Type = Null
|
||||
value.Raw = tolit(json[i:])
|
||||
value.Str, value.Num = "", 0
|
||||
case 't':
|
||||
value.Type = True
|
||||
value.Raw = tolit(json[i:])
|
||||
value.Str, value.Num = "", 0
|
||||
case 'f':
|
||||
value.Type = False
|
||||
value.Raw = tolit(json[i:])
|
||||
value.Str, value.Num = "", 0
|
||||
case '"':
|
||||
value.Type = String
|
||||
value.Raw, value.Str = tostr(json[i:])
|
||||
value.Num = 0
|
||||
}
|
||||
i += len(value.Raw) - 1
|
||||
|
||||
@@ -371,9 +389,13 @@ func (t Result) arrayOrMap(vc byte, valueize bool) (r arrayOrMapResult) {
|
||||
key = value
|
||||
} else {
|
||||
if valueize {
|
||||
r.oi[key.Str] = value.Value()
|
||||
if _, ok := r.oi[key.Str]; !ok {
|
||||
r.oi[key.Str] = value.Value()
|
||||
}
|
||||
} else {
|
||||
r.o[key.Str] = value
|
||||
if _, ok := r.o[key.Str]; !ok {
|
||||
r.o[key.Str] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
count++
|
||||
@@ -583,6 +605,8 @@ func (t Result) Exists() bool {
|
||||
// Number, for JSON numbers
|
||||
// string, for JSON string literals
|
||||
// nil, for JSON null
|
||||
// map[string]interface{}, for JSON objects
|
||||
// []interface{}, for JSON arrays
|
||||
//
|
||||
func (t Result) Value() interface{} {
|
||||
if t.Type == String {
|
||||
@@ -1287,7 +1311,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
|
||||
if rp.alogok {
|
||||
break
|
||||
}
|
||||
c.value.Raw = val
|
||||
c.value.Raw = ""
|
||||
c.value.Type = Number
|
||||
c.value.Num = float64(h - 1)
|
||||
c.calcd = true
|
||||
@@ -1383,64 +1407,14 @@ func Get(json, path string) Result {
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(c.value.Raw) > 0 && !c.calcd {
|
||||
jhdr := *(*reflect.StringHeader)(unsafe.Pointer(&json))
|
||||
rhdr := *(*reflect.StringHeader)(unsafe.Pointer(&(c.value.Raw)))
|
||||
c.value.Index = int(rhdr.Data - jhdr.Data)
|
||||
if c.value.Index < 0 || c.value.Index >= len(json) {
|
||||
c.value.Index = 0
|
||||
}
|
||||
}
|
||||
fillIndex(json, c)
|
||||
return c.value
|
||||
}
|
||||
func fromBytesGet(result Result) Result {
|
||||
// safely get the string headers
|
||||
rawhi := *(*reflect.StringHeader)(unsafe.Pointer(&result.Raw))
|
||||
strhi := *(*reflect.StringHeader)(unsafe.Pointer(&result.Str))
|
||||
// create byte slice headers
|
||||
rawh := reflect.SliceHeader{Data: rawhi.Data, Len: rawhi.Len}
|
||||
strh := reflect.SliceHeader{Data: strhi.Data, Len: strhi.Len}
|
||||
if strh.Data == 0 {
|
||||
// str is nil
|
||||
if rawh.Data == 0 {
|
||||
// raw is nil
|
||||
result.Raw = ""
|
||||
} else {
|
||||
// raw has data, safely copy the slice header to a string
|
||||
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
|
||||
}
|
||||
result.Str = ""
|
||||
} else if rawh.Data == 0 {
|
||||
// raw is nil
|
||||
result.Raw = ""
|
||||
// str has data, safely copy the slice header to a string
|
||||
result.Str = string(*(*[]byte)(unsafe.Pointer(&strh)))
|
||||
} else if strh.Data >= rawh.Data &&
|
||||
int(strh.Data)+strh.Len <= int(rawh.Data)+rawh.Len {
|
||||
// Str is a substring of Raw.
|
||||
start := int(strh.Data - rawh.Data)
|
||||
// safely copy the raw slice header
|
||||
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
|
||||
// substring the raw
|
||||
result.Str = result.Raw[start : start+strh.Len]
|
||||
} else {
|
||||
// safely copy both the raw and str slice headers to strings
|
||||
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
|
||||
result.Str = string(*(*[]byte)(unsafe.Pointer(&strh)))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetBytes searches json for the specified path.
|
||||
// If working with bytes, this method preferred over Get(string(data), path)
|
||||
func GetBytes(json []byte, path string) Result {
|
||||
var result Result
|
||||
if json != nil {
|
||||
// unsafe cast to string
|
||||
result = Get(*(*string)(unsafe.Pointer(&json)), path)
|
||||
result = fromBytesGet(result)
|
||||
}
|
||||
return result
|
||||
return getBytes(json, path)
|
||||
}
|
||||
|
||||
// runeit returns the rune from the the \uXXXX
|
||||
@@ -1863,8 +1837,14 @@ func validobject(data []byte, i int) (outi int, ok bool) {
|
||||
if data[i] == '}' {
|
||||
return i + 1, true
|
||||
}
|
||||
i++
|
||||
for ; i < len(data); i++ {
|
||||
if data[i] == '"' {
|
||||
switch data[i] {
|
||||
default:
|
||||
return i, false
|
||||
case ' ', '\t', '\n', '\r':
|
||||
continue
|
||||
case '"':
|
||||
goto key
|
||||
}
|
||||
}
|
||||
@@ -2056,6 +2036,20 @@ func Valid(json string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// ValidBytes returns true if the input is valid json.
|
||||
//
|
||||
// if !gjson.Valid(json) {
|
||||
// return errors.New("invalid json")
|
||||
// }
|
||||
// value := gjson.Get(json, "name.last")
|
||||
//
|
||||
// If working with bytes, this method preferred over Valid(string(data))
|
||||
//
|
||||
func ValidBytes(json []byte) bool {
|
||||
_, ok := validpayload(json, 0)
|
||||
return ok
|
||||
}
|
||||
|
||||
func parseUint(s string) (n uint64, ok bool) {
|
||||
var i int
|
||||
if i == len(s) {
|
||||
|
||||
10
gjson_gae.go
Normal file
10
gjson_gae.go
Normal file
@@ -0,0 +1,10 @@
|
||||
//+build appengine
|
||||
|
||||
package gjson
|
||||
|
||||
func getBytes(json []byte, path string) Result {
|
||||
return Get(string(json), path)
|
||||
}
|
||||
func fillIndex(json string, c *parseContext) {
|
||||
// noop. Use zero for the Index value.
|
||||
}
|
||||
73
gjson_ngae.go
Normal file
73
gjson_ngae.go
Normal file
@@ -0,0 +1,73 @@
|
||||
//+build !appengine
|
||||
|
||||
package gjson
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// getBytes casts the input json bytes to a string and safely returns the
|
||||
// results as uniquely allocated data. This operation is intended to minimize
|
||||
// copies and allocations for the large json string->[]byte.
|
||||
func getBytes(json []byte, path string) Result {
|
||||
var result Result
|
||||
if json != nil {
|
||||
// unsafe cast to string
|
||||
result = Get(*(*string)(unsafe.Pointer(&json)), path)
|
||||
result = fromBytesGet(result)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func fromBytesGet(result Result) Result {
|
||||
// safely get the string headers
|
||||
rawhi := *(*reflect.StringHeader)(unsafe.Pointer(&result.Raw))
|
||||
strhi := *(*reflect.StringHeader)(unsafe.Pointer(&result.Str))
|
||||
// create byte slice headers
|
||||
rawh := reflect.SliceHeader{Data: rawhi.Data, Len: rawhi.Len}
|
||||
strh := reflect.SliceHeader{Data: strhi.Data, Len: strhi.Len}
|
||||
if strh.Data == 0 {
|
||||
// str is nil
|
||||
if rawh.Data == 0 {
|
||||
// raw is nil
|
||||
result.Raw = ""
|
||||
} else {
|
||||
// raw has data, safely copy the slice header to a string
|
||||
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
|
||||
}
|
||||
result.Str = ""
|
||||
} else if rawh.Data == 0 {
|
||||
// raw is nil
|
||||
result.Raw = ""
|
||||
// str has data, safely copy the slice header to a string
|
||||
result.Str = string(*(*[]byte)(unsafe.Pointer(&strh)))
|
||||
} else if strh.Data >= rawh.Data &&
|
||||
int(strh.Data)+strh.Len <= int(rawh.Data)+rawh.Len {
|
||||
// Str is a substring of Raw.
|
||||
start := int(strh.Data - rawh.Data)
|
||||
// safely copy the raw slice header
|
||||
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
|
||||
// substring the raw
|
||||
result.Str = result.Raw[start : start+strh.Len]
|
||||
} else {
|
||||
// safely copy both the raw and str slice headers to strings
|
||||
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
|
||||
result.Str = string(*(*[]byte)(unsafe.Pointer(&strh)))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// fillIndex finds the position of Raw data and assigns it to the Index field
|
||||
// of the resulting value. If the position cannot be found then Index zero is
|
||||
// used instead.
|
||||
func fillIndex(json string, c *parseContext) {
|
||||
if len(c.value.Raw) > 0 && !c.calcd {
|
||||
jhdr := *(*reflect.StringHeader)(unsafe.Pointer(&json))
|
||||
rhdr := *(*reflect.StringHeader)(unsafe.Pointer(&(c.value.Raw)))
|
||||
c.value.Index = int(rhdr.Data - jhdr.Data)
|
||||
if c.value.Index < 0 || c.value.Index >= len(json) {
|
||||
c.value.Index = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
213
gjson_test.go
213
gjson_test.go
@@ -479,7 +479,8 @@ func TestBasic4(t *testing.T) {
|
||||
}
|
||||
token = get(basicJSON, "arr.#")
|
||||
if token.String() != "6" {
|
||||
t.Fatal("expecting '6'", "got", token.String())
|
||||
fmt.Printf("%#v\n", token)
|
||||
t.Fatal("expecting 6", "got", token.String())
|
||||
}
|
||||
token = get(basicJSON, "arr.3.hello")
|
||||
if token.String() != "world" {
|
||||
@@ -970,80 +971,82 @@ func TestUnmarshal(t *testing.T) {
|
||||
assert(t, str == Get(complicatedJSON, "LeftOut").String())
|
||||
}
|
||||
|
||||
func testvalid(json string, expect bool) {
|
||||
func testvalid(t *testing.T, json string, expect bool) {
|
||||
t.Helper()
|
||||
_, ok := validpayload([]byte(json), 0)
|
||||
if ok != expect {
|
||||
panic("mismatch")
|
||||
t.Fatal("mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidBasic(t *testing.T) {
|
||||
testvalid("0", true)
|
||||
testvalid("00", false)
|
||||
testvalid("-00", false)
|
||||
testvalid("-.", false)
|
||||
testvalid("0.0", true)
|
||||
testvalid("10.0", true)
|
||||
testvalid("10e1", true)
|
||||
testvalid("10EE", false)
|
||||
testvalid("10E-", false)
|
||||
testvalid("10E+", false)
|
||||
testvalid("10E123", true)
|
||||
testvalid("10E-123", true)
|
||||
testvalid("10E-0123", true)
|
||||
testvalid("", false)
|
||||
testvalid(" ", false)
|
||||
testvalid("{}", true)
|
||||
testvalid("{", false)
|
||||
testvalid("-", false)
|
||||
testvalid("-1", true)
|
||||
testvalid("-1.", false)
|
||||
testvalid("-1.0", true)
|
||||
testvalid(" -1.0", true)
|
||||
testvalid(" -1.0 ", true)
|
||||
testvalid("-1.0 ", true)
|
||||
testvalid("-1.0 i", false)
|
||||
testvalid("-1.0 i", false)
|
||||
testvalid("true", true)
|
||||
testvalid(" true", true)
|
||||
testvalid(" true ", true)
|
||||
testvalid(" True ", false)
|
||||
testvalid(" tru", false)
|
||||
testvalid("false", true)
|
||||
testvalid(" false", true)
|
||||
testvalid(" false ", true)
|
||||
testvalid(" False ", false)
|
||||
testvalid(" fals", false)
|
||||
testvalid("null", true)
|
||||
testvalid(" null", true)
|
||||
testvalid(" null ", true)
|
||||
testvalid(" Null ", false)
|
||||
testvalid(" nul", false)
|
||||
testvalid(" []", true)
|
||||
testvalid(" [true]", true)
|
||||
testvalid(" [ true, null ]", true)
|
||||
testvalid(" [ true,]", false)
|
||||
testvalid(`{"hello":"world"}`, true)
|
||||
testvalid(`{ "hello": "world" }`, true)
|
||||
testvalid(`{ "hello": "world", }`, false)
|
||||
testvalid(`{"a":"b",}`, false)
|
||||
testvalid(`{"a":"b","a"}`, false)
|
||||
testvalid(`{"a":"b","a":}`, false)
|
||||
testvalid(`{"a":"b","a":1}`, true)
|
||||
testvalid(`{"a":"b","a": 1, "c":{"hi":"there"} }`, true)
|
||||
testvalid(`{"a":"b","a": 1, "c":{"hi":"there", "easy":["going",{"mixed":"bag"}]} }`, true)
|
||||
testvalid(`""`, true)
|
||||
testvalid(`"`, false)
|
||||
testvalid(`"\n"`, true)
|
||||
testvalid(`"\"`, false)
|
||||
testvalid(`"\\"`, true)
|
||||
testvalid(`"a\\b"`, true)
|
||||
testvalid(`"a\\b\\\"a"`, true)
|
||||
testvalid(`"a\\b\\\uFFAAa"`, true)
|
||||
testvalid(`"a\\b\\\uFFAZa"`, false)
|
||||
testvalid(`"a\\b\\\uFFA"`, false)
|
||||
testvalid(string(complicatedJSON), true)
|
||||
testvalid(string(exampleJSON), true)
|
||||
testvalid(t, "0", true)
|
||||
testvalid(t, "00", false)
|
||||
testvalid(t, "-00", false)
|
||||
testvalid(t, "-.", false)
|
||||
testvalid(t, "0.0", true)
|
||||
testvalid(t, "10.0", true)
|
||||
testvalid(t, "10e1", true)
|
||||
testvalid(t, "10EE", false)
|
||||
testvalid(t, "10E-", false)
|
||||
testvalid(t, "10E+", false)
|
||||
testvalid(t, "10E123", true)
|
||||
testvalid(t, "10E-123", true)
|
||||
testvalid(t, "10E-0123", true)
|
||||
testvalid(t, "", false)
|
||||
testvalid(t, " ", false)
|
||||
testvalid(t, "{}", true)
|
||||
testvalid(t, "{", false)
|
||||
testvalid(t, "-", false)
|
||||
testvalid(t, "-1", true)
|
||||
testvalid(t, "-1.", false)
|
||||
testvalid(t, "-1.0", true)
|
||||
testvalid(t, " -1.0", true)
|
||||
testvalid(t, " -1.0 ", true)
|
||||
testvalid(t, "-1.0 ", true)
|
||||
testvalid(t, "-1.0 i", false)
|
||||
testvalid(t, "-1.0 i", false)
|
||||
testvalid(t, "true", true)
|
||||
testvalid(t, " true", true)
|
||||
testvalid(t, " true ", true)
|
||||
testvalid(t, " True ", false)
|
||||
testvalid(t, " tru", false)
|
||||
testvalid(t, "false", true)
|
||||
testvalid(t, " false", true)
|
||||
testvalid(t, " false ", true)
|
||||
testvalid(t, " False ", false)
|
||||
testvalid(t, " fals", false)
|
||||
testvalid(t, "null", true)
|
||||
testvalid(t, " null", true)
|
||||
testvalid(t, " null ", true)
|
||||
testvalid(t, " Null ", false)
|
||||
testvalid(t, " nul", false)
|
||||
testvalid(t, " []", true)
|
||||
testvalid(t, " [true]", true)
|
||||
testvalid(t, " [ true, null ]", true)
|
||||
testvalid(t, " [ true,]", false)
|
||||
testvalid(t, `{"hello":"world"}`, true)
|
||||
testvalid(t, `{ "hello": "world" }`, true)
|
||||
testvalid(t, `{ "hello": "world", }`, false)
|
||||
testvalid(t, `{"a":"b",}`, false)
|
||||
testvalid(t, `{"a":"b","a"}`, false)
|
||||
testvalid(t, `{"a":"b","a":}`, false)
|
||||
testvalid(t, `{"a":"b","a":1}`, true)
|
||||
testvalid(t, `{"a":"b",2"1":2}`, false)
|
||||
testvalid(t, `{"a":"b","a": 1, "c":{"hi":"there"} }`, true)
|
||||
testvalid(t, `{"a":"b","a": 1, "c":{"hi":"there", "easy":["going",{"mixed":"bag"}]} }`, true)
|
||||
testvalid(t, `""`, true)
|
||||
testvalid(t, `"`, false)
|
||||
testvalid(t, `"\n"`, true)
|
||||
testvalid(t, `"\"`, false)
|
||||
testvalid(t, `"\\"`, true)
|
||||
testvalid(t, `"a\\b"`, true)
|
||||
testvalid(t, `"a\\b\\\"a"`, true)
|
||||
testvalid(t, `"a\\b\\\uFFAAa"`, true)
|
||||
testvalid(t, `"a\\b\\\uFFAZa"`, false)
|
||||
testvalid(t, `"a\\b\\\uFFA"`, false)
|
||||
testvalid(t, string(complicatedJSON), true)
|
||||
testvalid(t, string(exampleJSON), true)
|
||||
}
|
||||
|
||||
var jsonchars = []string{"{", "[", ",", ":", "}", "]", "1", "0", "true", "false", "null", `""`, `"\""`, `"a"`}
|
||||
@@ -1352,3 +1355,75 @@ null
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNumUint64String(t *testing.T) {
|
||||
i := 9007199254740993 //2^53 + 1
|
||||
j := fmt.Sprintf(`{"data": [ %d, "hello" ] }`, i)
|
||||
res := Get(j, "data.0")
|
||||
if res.String() != "9007199254740993" {
|
||||
t.Fatalf("expected '%v', got '%v'", "9007199254740993", res.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumInt64String(t *testing.T) {
|
||||
i := -9007199254740993
|
||||
j := fmt.Sprintf(`{"data":[ "hello", %d ]}`, i)
|
||||
res := Get(j, "data.1")
|
||||
if res.String() != "-9007199254740993" {
|
||||
t.Fatalf("expected '%v', got '%v'", "-9007199254740993", res.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumBigString(t *testing.T) {
|
||||
i := "900719925474099301239109123101" // very big
|
||||
j := fmt.Sprintf(`{"data":[ "hello", "%s" ]}`, i)
|
||||
res := Get(j, "data.1")
|
||||
if res.String() != "900719925474099301239109123101" {
|
||||
t.Fatalf("expected '%v', got '%v'", "900719925474099301239109123101", res.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumFloatString(t *testing.T) {
|
||||
i := -9007199254740993
|
||||
j := fmt.Sprintf(`{"data":[ "hello", %d ]}`, i) //No quotes around value!!
|
||||
res := Get(j, "data.1")
|
||||
if res.String() != "-9007199254740993" {
|
||||
t.Fatalf("expected '%v', got '%v'", "-9007199254740993", res.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuplicateKeys(t *testing.T) {
|
||||
// this is vaild json according to the JSON spec
|
||||
var json = `{"name": "Alex","name": "Peter"}`
|
||||
if Parse(json).Get("name").String() !=
|
||||
Parse(json).Map()["name"].String() {
|
||||
t.Fatalf("expected '%v', got '%v'",
|
||||
Parse(json).Get("name").String(),
|
||||
Parse(json).Map()["name"].String(),
|
||||
)
|
||||
}
|
||||
if !Valid(json) {
|
||||
t.Fatal("should be valid")
|
||||
}
|
||||
}
|
||||
|
||||
func TestArrayValues(t *testing.T) {
|
||||
var json = `{"array": ["PERSON1","PERSON2",0],}`
|
||||
values := Get(json, "array").Array()
|
||||
var output string
|
||||
for i, val := range values {
|
||||
if i > 0 {
|
||||
output += "\n"
|
||||
}
|
||||
output += fmt.Sprintf("%#v", val)
|
||||
}
|
||||
expect := strings.Join([]string{
|
||||
`gjson.Result{Type:3, Raw:"\"PERSON1\"", Str:"PERSON1", Num:0, Index:0}`,
|
||||
`gjson.Result{Type:3, Raw:"\"PERSON2\"", Str:"PERSON2", Num:0, Index:0}`,
|
||||
`gjson.Result{Type:2, Raw:"0", Str:"", Num:0, Index:0}`,
|
||||
}, "\n")
|
||||
if output != expect {
|
||||
t.Fatalf("expected '%v', got '%v'", expect, output)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user