mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-03 19:12:25 -06:00 
			
		
		
		
	
		
			
				
	
	
		
			1283 lines
		
	
	
	
		
			29 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1283 lines
		
	
	
	
		
			29 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package jsonparser
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"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")
 | 
						|
	OverflowIntegerError       = errors.New("Value is number, but overflowed while parsing")
 | 
						|
	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)
 | 
						|
}
 | 
						|
 | 
						|
func findTokenStart(data []byte, token byte) int {
 | 
						|
	for i := len(data) - 1; i >= 0; i-- {
 | 
						|
		switch data[i] {
 | 
						|
		case token:
 | 
						|
			return i
 | 
						|
		case '[', '{':
 | 
						|
			return 0
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return 0
 | 
						|
}
 | 
						|
 | 
						|
func findKeyStart(data []byte, key string) (int, error) {
 | 
						|
	i := 0
 | 
						|
	ln := len(data)
 | 
						|
	if ln > 0 && (data[0] == '{' || data[0] == '[') {
 | 
						|
		i = 1
 | 
						|
	}
 | 
						|
	var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings
 | 
						|
 | 
						|
	if ku, err := Unescape(StringToBytes(key), stackbuf[:]); err == nil {
 | 
						|
		key = bytesToString(&ku)
 | 
						|
	}
 | 
						|
 | 
						|
	for i < ln {
 | 
						|
		switch data[i] {
 | 
						|
		case '"':
 | 
						|
			i++
 | 
						|
			keyBegin := i
 | 
						|
 | 
						|
			strEnd, keyEscaped := stringEnd(data[i:])
 | 
						|
			if strEnd == -1 {
 | 
						|
				break
 | 
						|
			}
 | 
						|
			i += strEnd
 | 
						|
			keyEnd := i - 1
 | 
						|
 | 
						|
			valueOffset := nextToken(data[i:])
 | 
						|
			if valueOffset == -1 {
 | 
						|
				break
 | 
						|
			}
 | 
						|
 | 
						|
			i += valueOffset
 | 
						|
 | 
						|
			// if string is a key, and key level match
 | 
						|
			k := 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
 | 
						|
			if keyEscaped {
 | 
						|
				if ku, err := Unescape(k, stackbuf[:]); err != nil {
 | 
						|
					break
 | 
						|
				} else {
 | 
						|
					k = ku
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if data[i] == ':' && len(key) == len(k) && bytesToString(&k) == key {
 | 
						|
				return keyBegin - 1, nil
 | 
						|
			}
 | 
						|
 | 
						|
		case '[':
 | 
						|
			end := blockEnd(data[i:], data[i], ']')
 | 
						|
			if end != -1 {
 | 
						|
				i = i + end
 | 
						|
			}
 | 
						|
		case '{':
 | 
						|
			end := blockEnd(data[i:], data[i], '}')
 | 
						|
			if end != -1 {
 | 
						|
				i = i + end
 | 
						|
			}
 | 
						|
		}
 | 
						|
		i++
 | 
						|
	}
 | 
						|
 | 
						|
	return -1, KeyPathNotFoundError
 | 
						|
}
 | 
						|
 | 
						|
func tokenStart(data []byte) int {
 | 
						|
	for i := len(data) - 1; i >= 0; i-- {
 | 
						|
		switch data[i] {
 | 
						|
		case '\n', '\r', '\t', ',', '{', '[':
 | 
						|
			return i
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return 0
 | 
						|
}
 | 
						|
 | 
						|
// 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
 | 
						|
}
 | 
						|
 | 
						|
// Find position of last character which is not whitespace
 | 
						|
func lastToken(data []byte) int {
 | 
						|
	for i := len(data) - 1; i >= 0; i-- {
 | 
						|
		switch data[i] {
 | 
						|
		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)
 | 
						|
	lastMatched := true
 | 
						|
 | 
						|
	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
 | 
						|
			if data[i] == ':' {
 | 
						|
				if level < 1 {
 | 
						|
					return -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 level <= len(keys) {
 | 
						|
					if equalStr(&keyUnesc, keys[level-1]) {
 | 
						|
						lastMatched = true
 | 
						|
 | 
						|
						// if key level match
 | 
						|
						if keyLevel == level-1 {
 | 
						|
							keyLevel++
 | 
						|
							// If we found all keys in path
 | 
						|
							if keyLevel == lk {
 | 
						|
								return i + 1
 | 
						|
							}
 | 
						|
						}
 | 
						|
					} else {
 | 
						|
						lastMatched = false
 | 
						|
					}
 | 
						|
				} else {
 | 
						|
					return -1
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				i--
 | 
						|
			}
 | 
						|
		case '{':
 | 
						|
 | 
						|
			// in case parent key is matched then only we will increase the level otherwise can directly
 | 
						|
			// can move to the end of this block
 | 
						|
			if !lastMatched {
 | 
						|
				end := blockEnd(data[i:], '{', '}')
 | 
						|
				if end == -1 {
 | 
						|
					return -1
 | 
						|
				}
 | 
						|
				i += end - 1
 | 
						|
			} else {
 | 
						|
				level++
 | 
						|
			}
 | 
						|
		case '}':
 | 
						|
			level--
 | 
						|
			if level == keyLevel {
 | 
						|
				keyLevel--
 | 
						|
			}
 | 
						|
		case '[':
 | 
						|
			// If we want to get array element by index
 | 
						|
			if keyLevel == level && keys[level][0] == '[' {
 | 
						|
				var keyLen = len(keys[level])
 | 
						|
				if keyLen < 3 || keys[level][0] != '[' || keys[level][keyLen-1] != ']' {
 | 
						|
					return -1
 | 
						|
				}
 | 
						|
				aIdx, err := strconv.Atoi(keys[level][1 : keyLen-1])
 | 
						|
				if err != nil {
 | 
						|
					return -1
 | 
						|
				}
 | 
						|
				var curIdx int
 | 
						|
				var valueFound []byte
 | 
						|
				var valueOffset int
 | 
						|
				var curI = i
 | 
						|
				ArrayEach(data[i:], func(value []byte, dataType ValueType, offset int, err error) {
 | 
						|
					if curIdx == aIdx {
 | 
						|
						valueFound = value
 | 
						|
						valueOffset = offset
 | 
						|
						if dataType == String {
 | 
						|
							valueOffset = valueOffset - 2
 | 
						|
							valueFound = data[curI+valueOffset : curI+valueOffset+len(value)+2]
 | 
						|
						}
 | 
						|
					}
 | 
						|
					curIdx += 1
 | 
						|
				})
 | 
						|
 | 
						|
				if valueFound == nil {
 | 
						|
					return -1
 | 
						|
				} else {
 | 
						|
					subIndex := searchKeys(valueFound, keys[level+1:]...)
 | 
						|
					if subIndex < 0 {
 | 
						|
						return -1
 | 
						|
					}
 | 
						|
					return i + valueOffset + subIndex
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				// Do not search for keys inside arrays
 | 
						|
				if arraySkip := blockEnd(data[i:], '[', ']'); arraySkip == -1 {
 | 
						|
					return -1
 | 
						|
				} else {
 | 
						|
					i += arraySkip - 1
 | 
						|
				}
 | 
						|
			}
 | 
						|
		case ':': // If encountered, JSON data is malformed
 | 
						|
			return -1
 | 
						|
		}
 | 
						|
 | 
						|
		i++
 | 
						|
	}
 | 
						|
 | 
						|
	return -1
 | 
						|
}
 | 
						|
 | 
						|
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 x struct{}
 | 
						|
	pathFlags := make([]bool, len(paths))
 | 
						|
	var level, pathsMatched, i int
 | 
						|
	ln := len(data)
 | 
						|
 | 
						|
	var maxPath int
 | 
						|
	for _, p := range paths {
 | 
						|
		if len(p) > maxPath {
 | 
						|
			maxPath = len(p)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	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 {
 | 
						|
					var stackbuf [unescapeStackBufSize]byte
 | 
						|
					if ku, err := Unescape(key, stackbuf[:]); err != nil {
 | 
						|
						return -1
 | 
						|
					} else {
 | 
						|
						keyUnesc = ku
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				if maxPath >= level {
 | 
						|
					if level < 1 {
 | 
						|
						cb(-1, nil, Unknown, MalformedJsonError)
 | 
						|
						return -1
 | 
						|
					}
 | 
						|
 | 
						|
					pathsBuf[level-1] = bytesToString(&keyUnesc)
 | 
						|
					for pi, p := range paths {
 | 
						|
						if len(p) != level || pathFlags[pi] || !equalStr(&keyUnesc, p[level-1]) || !sameTree(p, pathsBuf[:level]) {
 | 
						|
							continue
 | 
						|
						}
 | 
						|
 | 
						|
						match = pi
 | 
						|
 | 
						|
						pathsMatched++
 | 
						|
						pathFlags[pi] = true
 | 
						|
 | 
						|
						v, dt, _, e := Get(data[i+1:])
 | 
						|
						cb(pi, v, dt, e)
 | 
						|
 | 
						|
						if pathsMatched == len(paths) {
 | 
						|
							break
 | 
						|
						}
 | 
						|
					}
 | 
						|
					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
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				if i < ln {
 | 
						|
					switch data[i] {
 | 
						|
					case '{', '}', '[', '"':
 | 
						|
						i--
 | 
						|
					}
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				i--
 | 
						|
			}
 | 
						|
		case '{':
 | 
						|
			level++
 | 
						|
		case '}':
 | 
						|
			level--
 | 
						|
		case '[':
 | 
						|
			var ok bool
 | 
						|
			arrIdxFlags := make(map[int]struct{})
 | 
						|
			pIdxFlags := make([]bool, len(paths))
 | 
						|
 | 
						|
			if level < 0 {
 | 
						|
				cb(-1, nil, Unknown, MalformedJsonError)
 | 
						|
				return -1
 | 
						|
			}
 | 
						|
 | 
						|
			for pi, p := range paths {
 | 
						|
				if len(p) < level+1 || pathFlags[pi] || p[level][0] != '[' || !sameTree(p, pathsBuf[:level]) {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				if len(p[level]) >= 2 {
 | 
						|
					aIdx, _ := strconv.Atoi(p[level][1 : len(p[level])-1])
 | 
						|
					arrIdxFlags[aIdx] = x
 | 
						|
					pIdxFlags[pi] = true
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if len(arrIdxFlags) > 0 {
 | 
						|
				level++
 | 
						|
 | 
						|
				var curIdx int
 | 
						|
				arrOff, _ := ArrayEach(data[i:], func(value []byte, dataType ValueType, offset int, err error) {
 | 
						|
					if _, ok = arrIdxFlags[curIdx]; ok {
 | 
						|
						for pi, p := range paths {
 | 
						|
							if pIdxFlags[pi] {
 | 
						|
								aIdx, _ := strconv.Atoi(p[level-1][1 : len(p[level-1])-1])
 | 
						|
 | 
						|
								if curIdx == aIdx {
 | 
						|
									of := searchKeys(value, p[level:]...)
 | 
						|
 | 
						|
									pathsMatched++
 | 
						|
									pathFlags[pi] = true
 | 
						|
 | 
						|
									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")
 | 
						|
)
 | 
						|
 | 
						|
func createInsertComponent(keys []string, setValue []byte, comma, object bool) []byte {
 | 
						|
	isIndex := string(keys[0][0]) == "["
 | 
						|
	offset := 0
 | 
						|
	lk := calcAllocateSpace(keys, setValue, comma, object)
 | 
						|
	buffer := make([]byte, lk, lk)
 | 
						|
	if comma {
 | 
						|
		offset += WriteToBuffer(buffer[offset:], ",")
 | 
						|
	}
 | 
						|
	if isIndex && !comma {
 | 
						|
		offset += WriteToBuffer(buffer[offset:], "[")
 | 
						|
	} else {
 | 
						|
		if object {
 | 
						|
			offset += WriteToBuffer(buffer[offset:], "{")
 | 
						|
		}
 | 
						|
		if !isIndex {
 | 
						|
			offset += WriteToBuffer(buffer[offset:], "\"")
 | 
						|
			offset += WriteToBuffer(buffer[offset:], keys[0])
 | 
						|
			offset += WriteToBuffer(buffer[offset:], "\":")
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for i := 1; i < len(keys); i++ {
 | 
						|
		if string(keys[i][0]) == "[" {
 | 
						|
			offset += WriteToBuffer(buffer[offset:], "[")
 | 
						|
		} else {
 | 
						|
			offset += WriteToBuffer(buffer[offset:], "{\"")
 | 
						|
			offset += WriteToBuffer(buffer[offset:], keys[i])
 | 
						|
			offset += WriteToBuffer(buffer[offset:], "\":")
 | 
						|
		}
 | 
						|
	}
 | 
						|
	offset += WriteToBuffer(buffer[offset:], string(setValue))
 | 
						|
	for i := len(keys) - 1; i > 0; i-- {
 | 
						|
		if string(keys[i][0]) == "[" {
 | 
						|
			offset += WriteToBuffer(buffer[offset:], "]")
 | 
						|
		} else {
 | 
						|
			offset += WriteToBuffer(buffer[offset:], "}")
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if isIndex && !comma {
 | 
						|
		offset += WriteToBuffer(buffer[offset:], "]")
 | 
						|
	}
 | 
						|
	if object && !isIndex {
 | 
						|
		offset += WriteToBuffer(buffer[offset:], "}")
 | 
						|
	}
 | 
						|
	return buffer
 | 
						|
}
 | 
						|
 | 
						|
func calcAllocateSpace(keys []string, setValue []byte, comma, object bool) int {
 | 
						|
	isIndex := string(keys[0][0]) == "["
 | 
						|
	lk := 0
 | 
						|
	if comma {
 | 
						|
		// ,
 | 
						|
		lk += 1
 | 
						|
	}
 | 
						|
	if isIndex && !comma {
 | 
						|
		// []
 | 
						|
		lk += 2
 | 
						|
	} else {
 | 
						|
		if object {
 | 
						|
			// {
 | 
						|
			lk += 1
 | 
						|
		}
 | 
						|
		if !isIndex {
 | 
						|
			// "keys[0]"
 | 
						|
			lk += len(keys[0]) + 3
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	lk += len(setValue)
 | 
						|
	for i := 1; i < len(keys); i++ {
 | 
						|
		if string(keys[i][0]) == "[" {
 | 
						|
			// []
 | 
						|
			lk += 2
 | 
						|
		} else {
 | 
						|
			// {"keys[i]":setValue}
 | 
						|
			lk += len(keys[i]) + 5
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if object && !isIndex {
 | 
						|
		// }
 | 
						|
		lk += 1
 | 
						|
	}
 | 
						|
 | 
						|
	return lk
 | 
						|
}
 | 
						|
 | 
						|
func WriteToBuffer(buffer []byte, str string) int {
 | 
						|
	copy(buffer, str)
 | 
						|
	return len(str)
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 | 
						|
Del - Receives existing data structure, path to delete.
 | 
						|
 | 
						|
Returns:
 | 
						|
`data` - return modified data
 | 
						|
 | 
						|
*/
 | 
						|
func Delete(data []byte, keys ...string) []byte {
 | 
						|
	lk := len(keys)
 | 
						|
	if lk == 0 {
 | 
						|
		return data[:0]
 | 
						|
	}
 | 
						|
 | 
						|
	array := false
 | 
						|
	if len(keys[lk-1]) > 0 && string(keys[lk-1][0]) == "[" {
 | 
						|
		array = true
 | 
						|
	}
 | 
						|
 | 
						|
	var startOffset, keyOffset int
 | 
						|
	endOffset := len(data)
 | 
						|
	var err error
 | 
						|
	if !array {
 | 
						|
		if len(keys) > 1 {
 | 
						|
			_, _, startOffset, endOffset, err = internalGet(data, keys[:lk-1]...)
 | 
						|
			if err == KeyPathNotFoundError {
 | 
						|
				// problem parsing the data
 | 
						|
				return data
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		keyOffset, err = findKeyStart(data[startOffset:endOffset], keys[lk-1])
 | 
						|
		if err == KeyPathNotFoundError {
 | 
						|
			// problem parsing the data
 | 
						|
			return data
 | 
						|
		}
 | 
						|
		keyOffset += startOffset
 | 
						|
		_, _, _, subEndOffset, _ := internalGet(data[startOffset:endOffset], keys[lk-1])
 | 
						|
		endOffset = startOffset + subEndOffset
 | 
						|
		tokEnd := tokenEnd(data[endOffset:])
 | 
						|
		tokStart := findTokenStart(data[:keyOffset], ","[0])
 | 
						|
 | 
						|
		if data[endOffset+tokEnd] == ","[0] {
 | 
						|
			endOffset += tokEnd + 1
 | 
						|
		} else if data[endOffset+tokEnd] == " "[0] && len(data) > endOffset+tokEnd+1 && data[endOffset+tokEnd+1] == ","[0] {
 | 
						|
			endOffset += tokEnd + 2
 | 
						|
		} else if data[endOffset+tokEnd] == "}"[0] && data[tokStart] == ","[0] {
 | 
						|
			keyOffset = tokStart
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		_, _, keyOffset, endOffset, err = internalGet(data, keys...)
 | 
						|
		if err == KeyPathNotFoundError {
 | 
						|
			// problem parsing the data
 | 
						|
			return data
 | 
						|
		}
 | 
						|
 | 
						|
		tokEnd := tokenEnd(data[endOffset:])
 | 
						|
		tokStart := findTokenStart(data[:keyOffset], ","[0])
 | 
						|
 | 
						|
		if data[endOffset+tokEnd] == ","[0] {
 | 
						|
			endOffset += tokEnd + 1
 | 
						|
		} else if data[endOffset+tokEnd] == "]"[0] && data[tokStart] == ","[0] {
 | 
						|
			keyOffset = tokStart
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// We need to remove remaining trailing comma if we delete las element in the object
 | 
						|
	prevTok := lastToken(data[:keyOffset])
 | 
						|
	remainedValue := data[endOffset:]
 | 
						|
 | 
						|
	var newOffset int
 | 
						|
	if nextToken(remainedValue) > -1 && remainedValue[nextToken(remainedValue)] == '}' && data[prevTok] == ',' {
 | 
						|
		newOffset = prevTok
 | 
						|
	} else {
 | 
						|
		newOffset = prevTok + 1
 | 
						|
	}
 | 
						|
 | 
						|
	// We have to make a copy here if we don't want to mangle the original data, because byte slices are
 | 
						|
	// accessed by reference and not by value
 | 
						|
	dataCopy := make([]byte, len(data))
 | 
						|
	copy(dataCopy, data)
 | 
						|
	data = append(dataCopy[:newOffset], dataCopy[endOffset:]...)
 | 
						|
 | 
						|
	return data
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 | 
						|
Set - Receives existing data structure, path to set, and data to set at that key.
 | 
						|
 | 
						|
Returns:
 | 
						|
`value` - modified byte array
 | 
						|
`err` - On any parsing error
 | 
						|
 | 
						|
*/
 | 
						|
func Set(data []byte, setValue []byte, keys ...string) (value []byte, err error) {
 | 
						|
	// ensure keys are set
 | 
						|
	if len(keys) == 0 {
 | 
						|
		return nil, KeyPathNotFoundError
 | 
						|
	}
 | 
						|
 | 
						|
	_, _, startOffset, endOffset, err := internalGet(data, keys...)
 | 
						|
	if err != nil {
 | 
						|
		if err != KeyPathNotFoundError {
 | 
						|
			// problem parsing the data
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		// full path doesnt exist
 | 
						|
		// does any subpath exist?
 | 
						|
		var depth int
 | 
						|
		for i := range keys {
 | 
						|
			_, _, start, end, sErr := internalGet(data, keys[:i+1]...)
 | 
						|
			if sErr != nil {
 | 
						|
				break
 | 
						|
			} else {
 | 
						|
				endOffset = end
 | 
						|
				startOffset = start
 | 
						|
				depth++
 | 
						|
			}
 | 
						|
		}
 | 
						|
		comma := true
 | 
						|
		object := false
 | 
						|
		if endOffset == -1 {
 | 
						|
			firstToken := nextToken(data)
 | 
						|
			// We can't set a top-level key if data isn't an object
 | 
						|
			if firstToken < 0 || data[firstToken] != '{' {
 | 
						|
				return nil, KeyPathNotFoundError
 | 
						|
			}
 | 
						|
			// Don't need a comma if the input is an empty object
 | 
						|
			secondToken := firstToken + 1 + nextToken(data[firstToken+1:])
 | 
						|
			if data[secondToken] == '}' {
 | 
						|
				comma = false
 | 
						|
			}
 | 
						|
			// Set the top level key at the end (accounting for any trailing whitespace)
 | 
						|
			// This assumes last token is valid like '}', could check and return error
 | 
						|
			endOffset = lastToken(data)
 | 
						|
		}
 | 
						|
		depthOffset := endOffset
 | 
						|
		if depth != 0 {
 | 
						|
			// if subpath is a non-empty object, add to it
 | 
						|
			// or if subpath is a non-empty array, add to it
 | 
						|
			if (data[startOffset] == '{' && data[startOffset+1+nextToken(data[startOffset+1:])] != '}') ||
 | 
						|
				(data[startOffset] == '[' && data[startOffset+1+nextToken(data[startOffset+1:])] == '{') && keys[depth:][0][0] == 91 {
 | 
						|
				depthOffset--
 | 
						|
				startOffset = depthOffset
 | 
						|
				// otherwise, over-write it with a new object
 | 
						|
			} else {
 | 
						|
				comma = false
 | 
						|
				object = true
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			startOffset = depthOffset
 | 
						|
		}
 | 
						|
		value = append(data[:startOffset], append(createInsertComponent(keys[depth:], setValue, comma, object), data[depthOffset:]...)...)
 | 
						|
	} else {
 | 
						|
		// path currently exists
 | 
						|
		startComponent := data[:startOffset]
 | 
						|
		endComponent := data[endOffset:]
 | 
						|
 | 
						|
		value = make([]byte, len(startComponent)+len(endComponent)+len(setValue))
 | 
						|
		newEndOffset := startOffset + len(setValue)
 | 
						|
		copy(value[0:startOffset], startComponent)
 | 
						|
		copy(value[startOffset:newEndOffset], setValue)
 | 
						|
		copy(value[newEndOffset:], endComponent)
 | 
						|
	}
 | 
						|
	return value, nil
 | 
						|
}
 | 
						|
 | 
						|
func getType(data []byte, offset int) ([]byte, ValueType, int, error) {
 | 
						|
	var dataType ValueType
 | 
						|
	endOffset := offset
 | 
						|
 | 
						|
	// if string value
 | 
						|
	if data[offset] == '"' {
 | 
						|
		dataType = String
 | 
						|
		if idx, _ := stringEnd(data[offset+1:]); idx != -1 {
 | 
						|
			endOffset += idx + 1
 | 
						|
		} else {
 | 
						|
			return nil, 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 nil, 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 nil, 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
 | 
						|
	}
 | 
						|
	return data[offset:endOffset], dataType, endOffset, nil
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
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) {
 | 
						|
	a, b, _, d, e := internalGet(data, keys...)
 | 
						|
	return a, b, d, e
 | 
						|
}
 | 
						|
 | 
						|
func internalGet(data []byte, keys ...string) (value []byte, dataType ValueType, offset, endOffset int, err error) {
 | 
						|
	if len(keys) > 0 {
 | 
						|
		if offset = searchKeys(data, keys...); offset == -1 {
 | 
						|
			return nil, NotExist, -1, -1, KeyPathNotFoundError
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Go to closest value
 | 
						|
	nO := nextToken(data[offset:])
 | 
						|
	if nO == -1 {
 | 
						|
		return nil, NotExist, offset, -1, MalformedJsonError
 | 
						|
	}
 | 
						|
 | 
						|
	offset += nO
 | 
						|
	value, dataType, endOffset, err = getType(data, offset)
 | 
						|
	if err != nil {
 | 
						|
		return value, dataType, offset, endOffset, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Strip quotes from string values
 | 
						|
	if dataType == String {
 | 
						|
		value = value[1 : len(value)-1]
 | 
						|
	}
 | 
						|
 | 
						|
	return value[:len(value):len(value)], dataType, offset, 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
 | 
						|
	}
 | 
						|
 | 
						|
	nT := nextToken(data)
 | 
						|
	if nT == -1 {
 | 
						|
		return -1, MalformedJsonError
 | 
						|
	}
 | 
						|
 | 
						|
	offset = nT + 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) {
 | 
						|
	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 {
 | 
						|
			var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings
 | 
						|
			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 content
 | 
						|
	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 "", MalformedValueError
 | 
						|
	} 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, overflow := parseInt(b); !ok {
 | 
						|
		if overflow {
 | 
						|
			return 0, OverflowIntegerError
 | 
						|
		}
 | 
						|
		return 0, MalformedValueError
 | 
						|
	} else {
 | 
						|
		return v, nil
 | 
						|
	}
 | 
						|
}
 |