diff options
author | 2025-01-14 14:23:28 +0000 | |
---|---|---|
committer | 2025-01-14 14:23:28 +0000 | |
commit | b8ef9fc4bcccc6c024edaa8e9c91a6bf87f83dd9 (patch) | |
tree | 68eaf966c80237e18993e887c8583355f0943ca7 /vendor/github.com/buger/jsonparser/parser.go | |
parent | [chore] better dns validation (#3644) (diff) | |
download | gotosocial-b8ef9fc4bcccc6c024edaa8e9c91a6bf87f83dd9.tar.xz |
bump uptrace/bun dependencies from 1.2.6 to 1.2.8 (#3645)
Diffstat (limited to 'vendor/github.com/buger/jsonparser/parser.go')
-rw-r--r-- | vendor/github.com/buger/jsonparser/parser.go | 1283 |
1 files changed, 0 insertions, 1283 deletions
diff --git a/vendor/github.com/buger/jsonparser/parser.go b/vendor/github.com/buger/jsonparser/parser.go deleted file mode 100644 index 14b80bc48..000000000 --- a/vendor/github.com/buger/jsonparser/parser.go +++ /dev/null @@ -1,1283 +0,0 @@ -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 - } -} |