gorush/vendor/github.com/buger/jsonparser/parser.go

854 lines
19 KiB
Go
Raw Normal View History

package jsonparser
import (
"bytes"
"errors"
"fmt"
"math"
"strconv"
)
// Errors
var (
KeyPathNotFoundError = errors.New("Key path not found")
UnknownValueTypeError = errors.New("Unknown value type")
MalformedJsonError = errors.New("Malformed JSON error")
MalformedStringError = errors.New("Value is string, but can't find closing '\"' symbol")
MalformedArrayError = errors.New("Value is array, but can't find closing ']' symbol")
MalformedObjectError = errors.New("Value looks like object, but can't find closing '}' symbol")
MalformedValueError = errors.New("Value looks like Number/Boolean/None, but can't find its end: ',' or '}' symbol")
MalformedStringEscapeError = errors.New("Encountered an invalid escape sequence in a string")
)
// How much stack space to allocate for unescaping JSON strings; if a string longer
// than this needs to be escaped, it will result in a heap allocation
const unescapeStackBufSize = 64
func tokenEnd(data []byte) int {
for i, c := range data {
switch c {
case ' ', '\n', '\r', '\t', ',', '}', ']':
return i
}
}
return len(data)
}
// Find position of next character which is not whitespace
func nextToken(data []byte) int {
for i, c := range data {
switch c {
case ' ', '\n', '\r', '\t':
continue
default:
return i
}
}
return -1
}
// Tries to find the end of string
// Support if string contains escaped quote symbols.
func stringEnd(data []byte) (int, bool) {
escaped := false
for i, c := range data {
if c == '"' {
if !escaped {
return i + 1, false
} else {
j := i - 1
for {
if j < 0 || data[j] != '\\' {
return i + 1, true // even number of backslashes
}
j--
if j < 0 || data[j] != '\\' {
break // odd number of backslashes
}
j--
}
}
} else if c == '\\' {
escaped = true
}
}
return -1, escaped
}
// Find end of the data structure, array or object.
// For array openSym and closeSym will be '[' and ']', for object '{' and '}'
func blockEnd(data []byte, openSym byte, closeSym byte) int {
level := 0
i := 0
ln := len(data)
for i < ln {
switch data[i] {
case '"': // If inside string, skip it
se, _ := stringEnd(data[i+1:])
if se == -1 {
return -1
}
i += se
case openSym: // If open symbol, increase level
level++
case closeSym: // If close symbol, increase level
level--
// If we have returned to the original level, we're done
if level == 0 {
return i + 1
}
}
i++
}
return -1
}
func searchKeys(data []byte, keys ...string) int {
keyLevel := 0
level := 0
i := 0
ln := len(data)
lk := len(keys)
if lk == 0 {
return 0
}
var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings
for i < ln {
switch data[i] {
case '"':
i++
keyBegin := i
strEnd, keyEscaped := stringEnd(data[i:])
if strEnd == -1 {
return -1
}
i += strEnd
keyEnd := i - 1
valueOffset := nextToken(data[i:])
if valueOffset == -1 {
return -1
}
i += valueOffset
// if string is a key, and key level match
if data[i] == ':' && keyLevel == level-1 {
key := data[keyBegin:keyEnd]
// for unescape: if there are no escape sequences, this is cheap; if there are, it is a
// bit more expensive, but causes no allocations unless len(key) > unescapeStackBufSize
var keyUnesc []byte
if !keyEscaped {
keyUnesc = key
} else if ku, err := Unescape(key, stackbuf[:]); err != nil {
return -1
} else {
keyUnesc = ku
}
if equalStr(&keyUnesc, keys[level-1]) {
keyLevel++
// If we found all keys in path
if keyLevel == lk {
return i + 1
}
}
} else {
i--
}
case '{':
level++
case '}':
level--
if level == keyLevel {
keyLevel--
}
case '[':
// If we want to get array element by index
if keyLevel == level && keys[level][0] == '[' {
aIdx, _ := strconv.Atoi(keys[level][1 : len(keys[level])-1])
var curIdx int
var valueFound []byte
var valueOffset int
ArrayEach(data[i:], func(value []byte, dataType ValueType, offset int, err error) {
if curIdx == aIdx {
valueFound = value
valueOffset = offset
}
curIdx += 1
})
if valueFound == nil {
return -1
} else {
return i + valueOffset + searchKeys(valueFound, keys[level+1:]...)
}
} else {
// Do not search for keys inside arrays
if arraySkip := blockEnd(data[i:], '[', ']'); arraySkip == -1 {
return -1
} else {
i += arraySkip - 1
}
}
}
i++
}
return -1
}
var bitwiseFlags []int64
func init() {
for i := 0; i < 63; i++ {
bitwiseFlags = append(bitwiseFlags, int64(math.Pow(2, float64(i))))
}
}
func sameTree(p1, p2 []string) bool {
minLen := len(p1)
if len(p2) < minLen {
minLen = len(p2)
}
for pi_1, p_1 := range p1[:minLen] {
if p2[pi_1] != p_1 {
return false
}
}
return true
}
func EachKey(data []byte, cb func(int, []byte, ValueType, error), paths ...[]string) int {
var pathFlags int64
var level, pathsMatched, i int
ln := len(data)
var maxPath int
for _, p := range paths {
if len(p) > maxPath {
maxPath = len(p)
}
}
var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings
pathsBuf := make([]string, maxPath)
for i < ln {
switch data[i] {
case '"':
i++
keyBegin := i
strEnd, keyEscaped := stringEnd(data[i:])
if strEnd == -1 {
return -1
}
i += strEnd
keyEnd := i - 1
valueOffset := nextToken(data[i:])
if valueOffset == -1 {
return -1
}
i += valueOffset
// if string is a key, and key level match
if data[i] == ':' {
match := -1
key := data[keyBegin:keyEnd]
// for unescape: if there are no escape sequences, this is cheap; if there are, it is a
// bit more expensive, but causes no allocations unless len(key) > unescapeStackBufSize
var keyUnesc []byte
if !keyEscaped {
keyUnesc = key
} else if ku, err := Unescape(key, stackbuf[:]); err != nil {
return -1
} else {
keyUnesc = ku
}
if maxPath >= level {
pathsBuf[level-1] = bytesToString(&keyUnesc)
for pi, p := range paths {
if len(p) != level || pathFlags&bitwiseFlags[pi+1] != 0 || !equalStr(&keyUnesc, p[level-1]) || !sameTree(p, pathsBuf[:level]) {
continue
}
match = pi
i++
pathsMatched++
pathFlags |= bitwiseFlags[pi+1]
v, dt, of, e := Get(data[i:])
cb(pi, v, dt, e)
if of != -1 {
i += of
}
if pathsMatched == len(paths) {
return i
}
}
}
if match == -1 {
tokenOffset := nextToken(data[i+1:])
i += tokenOffset
if data[i] == '{' {
blockSkip := blockEnd(data[i:], '{', '}')
i += blockSkip + 1
}
}
switch data[i] {
case '{', '}', '[', '"':
i--
}
} else {
i--
}
case '{':
level++
case '}':
level--
case '[':
var arrIdxFlags int64
var pIdxFlags int64
for pi, p := range paths {
if len(p) < level+1 || pathFlags&bitwiseFlags[pi+1] != 0 || p[level][0] != '[' || !sameTree(p, pathsBuf[:level]) {
continue
}
aIdx, _ := strconv.Atoi(p[level][1 : len(p[level])-1])
arrIdxFlags |= bitwiseFlags[aIdx+1]
pIdxFlags |= bitwiseFlags[pi+1]
}
if arrIdxFlags > 0 {
level++
var curIdx int
arrOff, _ := ArrayEach(data[i:], func(value []byte, dataType ValueType, offset int, err error) {
if arrIdxFlags&bitwiseFlags[curIdx+1] != 0 {
for pi, p := range paths {
if pIdxFlags&bitwiseFlags[pi+1] != 0 {
aIdx, _ := strconv.Atoi(p[level-1][1 : len(p[level-1])-1])
if curIdx == aIdx {
of := searchKeys(value, p[level:]...)
pathsMatched++
pathFlags |= bitwiseFlags[pi+1]
if of != -1 {
v, dt, _, e := Get(value[of:])
cb(pi, v, dt, e)
}
}
}
}
}
curIdx += 1
})
if pathsMatched == len(paths) {
return i
}
i += arrOff - 1
} else {
// Do not search for keys inside arrays
if arraySkip := blockEnd(data[i:], '[', ']'); arraySkip == -1 {
return -1
} else {
i += arraySkip - 1
}
}
case ']':
level--
}
i++
}
return -1
}
// Data types available in valid JSON data.
type ValueType int
const (
NotExist = ValueType(iota)
String
Number
Object
Array
Boolean
Null
Unknown
)
func (vt ValueType) String() string {
switch vt {
case NotExist:
return "non-existent"
case String:
return "string"
case Number:
return "number"
case Object:
return "object"
case Array:
return "array"
case Boolean:
return "boolean"
case Null:
return "null"
default:
return "unknown"
}
}
var (
trueLiteral = []byte("true")
falseLiteral = []byte("false")
nullLiteral = []byte("null")
)
/*
Get - Receives data structure, and key path to extract value from.
Returns:
`value` - Pointer to original data structure containing key value, or just empty slice if nothing found or error
`dataType` - Can be: `NotExist`, `String`, `Number`, `Object`, `Array`, `Boolean` or `Null`
`offset` - Offset from provided data structure where key value ends. Used mostly internally, for example for `ArrayEach` helper.
`err` - If key not found or any other parsing issue it should return error. If key not found it also sets `dataType` to `NotExist`
Accept multiple keys to specify path to JSON value (in case of quering nested structures).
If no keys provided it will try to extract closest JSON value (simple ones or object/array), useful for reading streams or arrays, see `ArrayEach` implementation.
*/
func Get(data []byte, keys ...string) (value []byte, dataType ValueType, offset int, err error) {
if len(keys) > 0 {
if offset = searchKeys(data, keys...); offset == -1 {
return []byte{}, NotExist, -1, KeyPathNotFoundError
}
}
// Go to closest value
nO := nextToken(data[offset:])
if nO == -1 {
return []byte{}, NotExist, -1, MalformedJsonError
}
offset += nO
endOffset := offset
// if string value
if data[offset] == '"' {
dataType = String
if idx, _ := stringEnd(data[offset+1:]); idx != -1 {
endOffset += idx + 1
} else {
return []byte{}, dataType, offset, MalformedStringError
}
} else if data[offset] == '[' { // if array value
dataType = Array
// break label, for stopping nested loops
endOffset = blockEnd(data[offset:], '[', ']')
if endOffset == -1 {
return []byte{}, dataType, offset, MalformedArrayError
}
endOffset += offset
} else if data[offset] == '{' { // if object value
dataType = Object
// break label, for stopping nested loops
endOffset = blockEnd(data[offset:], '{', '}')
if endOffset == -1 {
return []byte{}, dataType, offset, MalformedObjectError
}
endOffset += offset
} else {
// Number, Boolean or None
end := tokenEnd(data[endOffset:])
if end == -1 {
return nil, dataType, offset, MalformedValueError
}
value := data[offset : endOffset+end]
switch data[offset] {
case 't', 'f': // true or false
if bytes.Equal(value, trueLiteral) || bytes.Equal(value, falseLiteral) {
dataType = Boolean
} else {
return nil, Unknown, offset, UnknownValueTypeError
}
case 'u', 'n': // undefined or null
if bytes.Equal(value, nullLiteral) {
dataType = Null
} else {
return nil, Unknown, offset, UnknownValueTypeError
}
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-':
dataType = Number
default:
return nil, Unknown, offset, UnknownValueTypeError
}
endOffset += end
}
value = data[offset:endOffset]
// Strip quotes from string values
if dataType == String {
value = value[1 : len(value)-1]
}
if dataType == Null {
value = []byte{}
}
return value, dataType, endOffset, nil
}
// ArrayEach is used when iterating arrays, accepts a callback function with the same return arguments as `Get`.
func ArrayEach(data []byte, cb func(value []byte, dataType ValueType, offset int, err error), keys ...string) (offset int, err error) {
if len(data) == 0 {
return -1, MalformedObjectError
}
offset = 1
if len(keys) > 0 {
if offset = searchKeys(data, keys...); offset == -1 {
return offset, KeyPathNotFoundError
}
// Go to closest value
nO := nextToken(data[offset:])
if nO == -1 {
return offset, MalformedJsonError
}
offset += nO
if data[offset] != '[' {
return offset, MalformedArrayError
}
offset++
}
nO := nextToken(data[offset:])
if nO == -1 {
return offset, MalformedJsonError
}
offset += nO
if data[offset] == ']' {
return offset, nil
}
for true {
v, t, o, e := Get(data[offset:])
if e != nil {
return offset, e
}
if o == 0 {
break
}
if t != NotExist {
cb(v, t, offset+o-len(v), e)
}
if e != nil {
break
}
offset += o
skipToToken := nextToken(data[offset:])
if skipToToken == -1 {
return offset, MalformedArrayError
}
offset += skipToToken
if data[offset] == ']' {
break
}
if data[offset] != ',' {
return offset, MalformedArrayError
}
offset++
}
return offset, nil
}
// ObjectEach iterates over the key-value pairs of a JSON object, invoking a given callback for each such entry
func ObjectEach(data []byte, callback func(key []byte, value []byte, dataType ValueType, offset int) error, keys ...string) (err error) {
var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings
offset := 0
// Descend to the desired key, if requested
if len(keys) > 0 {
if off := searchKeys(data, keys...); off == -1 {
return KeyPathNotFoundError
} else {
offset = off
}
}
// Validate and skip past opening brace
if off := nextToken(data[offset:]); off == -1 {
return MalformedObjectError
} else if offset += off; data[offset] != '{' {
return MalformedObjectError
} else {
offset++
}
// Skip to the first token inside the object, or stop if we find the ending brace
if off := nextToken(data[offset:]); off == -1 {
return MalformedJsonError
} else if offset += off; data[offset] == '}' {
return nil
}
// Loop pre-condition: data[offset] points to what should be either the next entry's key, or the closing brace (if it's anything else, the JSON is malformed)
for offset < len(data) {
// Step 1: find the next key
var key []byte
// Check what the the next token is: start of string, end of object, or something else (error)
switch data[offset] {
case '"':
offset++ // accept as string and skip opening quote
case '}':
return nil // we found the end of the object; stop and return success
default:
return MalformedObjectError
}
// Find the end of the key string
var keyEscaped bool
if off, esc := stringEnd(data[offset:]); off == -1 {
return MalformedJsonError
} else {
key, keyEscaped = data[offset:offset+off-1], esc
offset += off
}
// Unescape the string if needed
if keyEscaped {
if keyUnescaped, err := Unescape(key, stackbuf[:]); err != nil {
return MalformedStringEscapeError
} else {
key = keyUnescaped
}
}
// Step 2: skip the colon
if off := nextToken(data[offset:]); off == -1 {
return MalformedJsonError
} else if offset += off; data[offset] != ':' {
return MalformedJsonError
} else {
offset++
}
// Step 3: find the associated value, then invoke the callback
if value, valueType, off, err := Get(data[offset:]); err != nil {
return err
} else if err := callback(key, value, valueType, offset+off); err != nil { // Invoke the callback here!
return err
} else {
offset += off
}
// Step 4: skip over the next comma to the following token, or stop if we hit the ending brace
if off := nextToken(data[offset:]); off == -1 {
return MalformedArrayError
} else {
offset += off
switch data[offset] {
case '}':
return nil // Stop if we hit the close brace
case ',':
offset++ // Ignore the comma
default:
return MalformedObjectError
}
}
// Skip to the next token after the comma
if off := nextToken(data[offset:]); off == -1 {
return MalformedArrayError
} else {
offset += off
}
}
return MalformedObjectError // we shouldn't get here; it's expected that we will return via finding the ending brace
}
// GetUnsafeString returns the value retrieved by `Get`, use creates string without memory allocation by mapping string to slice memory. It does not handle escape symbols.
func GetUnsafeString(data []byte, keys ...string) (val string, err error) {
v, _, _, e := Get(data, keys...)
if e != nil {
return "", e
}
return bytesToString(&v), nil
}
// GetString returns the value retrieved by `Get`, cast to a string if possible, trying to properly handle escape and utf8 symbols
// If key data type do not match, it will return an error.
func GetString(data []byte, keys ...string) (val string, err error) {
v, t, _, e := Get(data, keys...)
if e != nil {
return "", e
}
if t != String {
return "", fmt.Errorf("Value is not a string: %s", string(v))
}
// If no escapes return raw conten
if bytes.IndexByte(v, '\\') == -1 {
return string(v), nil
}
return ParseString(v)
}
// GetFloat returns the value retrieved by `Get`, cast to a float64 if possible.
// The offset is the same as in `Get`.
// If key data type do not match, it will return an error.
func GetFloat(data []byte, keys ...string) (val float64, err error) {
v, t, _, e := Get(data, keys...)
if e != nil {
return 0, e
}
if t != Number {
return 0, fmt.Errorf("Value is not a number: %s", string(v))
}
return ParseFloat(v)
}
// GetInt returns the value retrieved by `Get`, cast to a int64 if possible.
// If key data type do not match, it will return an error.
func GetInt(data []byte, keys ...string) (val int64, err error) {
v, t, _, e := Get(data, keys...)
if e != nil {
return 0, e
}
if t != Number {
return 0, fmt.Errorf("Value is not a number: %s", string(v))
}
return ParseInt(v)
}
// GetBoolean returns the value retrieved by `Get`, cast to a bool if possible.
// The offset is the same as in `Get`.
// If key data type do not match, it will return error.
func GetBoolean(data []byte, keys ...string) (val bool, err error) {
v, t, _, e := Get(data, keys...)
if e != nil {
return false, e
}
if t != Boolean {
return false, fmt.Errorf("Value is not a boolean: %s", string(v))
}
return ParseBoolean(v)
}
// ParseBoolean parses a Boolean ValueType into a Go bool (not particularly useful, but here for completeness)
func ParseBoolean(b []byte) (bool, error) {
switch {
case bytes.Equal(b, trueLiteral):
return true, nil
case bytes.Equal(b, falseLiteral):
return false, nil
default:
return false, MalformedValueError
}
}
// ParseString parses a String ValueType into a Go string (the main parsing work is unescaping the JSON string)
func ParseString(b []byte) (string, error) {
var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings
if bU, err := Unescape(b, stackbuf[:]); err != nil {
return "", nil
} else {
return string(bU), nil
}
}
// ParseNumber parses a Number ValueType into a Go float64
func ParseFloat(b []byte) (float64, error) {
if v, err := parseFloat(&b); err != nil {
return 0, MalformedValueError
} else {
return v, nil
}
}
// ParseInt parses a Number ValueType into a Go int64
func ParseInt(b []byte) (int64, error) {
if v, ok := parseInt(b); !ok {
return 0, MalformedValueError
} else {
return v, nil
}
}