| 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") |
| NullValueError = errors.New("Value is null") |
| ) |
| |
| // 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 |
| |
| // SYS-REQ-044 |
| func tokenEnd(data []byte) int { |
| for i, c := range data { |
| switch c { |
| case ' ', '\n', '\r', '\t', ',', '}', ']': |
| return i |
| } |
| } |
| |
| return len(data) |
| } |
| |
| // SYS-REQ-001 |
| 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 |
| } |
| |
| // SYS-REQ-001, SYS-REQ-020, SYS-REQ-024 |
| func findKeyStart(data []byte, key string) (int, error) { |
| i := nextToken(data) |
| if i == -1 { |
| return i, KeyPathNotFoundError |
| } |
| ln := len(data) |
| // Note: nextToken returning non-negative (checked above) guarantees ln > 0, |
| // so the former ln > 0 guard was tautological and has been removed. |
| if data[i] == '{' || data[i] == '[' { |
| 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 |
| } |
| |
| // SYS-REQ-001 |
| func tokenStart(data []byte) int { |
| for i := len(data) - 1; i >= 0; i-- { |
| switch data[i] { |
| case '\n', '\r', '\t', ',', '{', '[': |
| return i |
| } |
| } |
| |
| return 0 |
| } |
| |
| // SYS-REQ-001 |
| // 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 |
| } |
| |
| // SYS-REQ-001 |
| // 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 |
| } |
| |
| // SYS-REQ-045 |
| // 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 |
| } |
| |
| // SYS-REQ-046 |
| // 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 |
| } |
| |
| // SYS-REQ-001, SYS-REQ-020, SYS-REQ-021, SYS-REQ-022, SYS-REQ-023, SYS-REQ-047 |
| 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] == '[' { |
| keyLen := len(keys[level]) |
| // Note: keys[level][0] == '[' is guaranteed by the outer if-guard, |
| // so the former middle term `keys[level][0] != '['` was always false |
| // (dead code) and has been removed. |
| if keyLen < 3 || 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 |
| 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 |
| } |
| |
| // SYS-REQ-008 |
| 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 |
| } |
| |
| const stackArraySize = 128 |
| |
| // SYS-REQ-008, SYS-REQ-085 |
| func EachKey(data []byte, cb func(int, []byte, ValueType, error), paths ...[]string) int { |
| var x struct{} |
| var level, pathsMatched, i int |
| ln := len(data) |
| |
| pathFlags := make([]bool, stackArraySize)[:] |
| if len(paths) > cap(pathFlags) { |
| pathFlags = make([]bool, len(paths))[:] |
| } |
| pathFlags = pathFlags[0:len(paths)] |
| |
| var maxPath int |
| for _, p := range paths { |
| if len(p) > maxPath { |
| maxPath = len(p) |
| } |
| } |
| |
| pathsBuf := make([]string, stackArraySize)[:] |
| if maxPath > cap(pathsBuf) { |
| pathsBuf = make([]string, maxPath)[:] |
| } |
| pathsBuf = pathsBuf[0: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 |
| // Note: i is now at the character BEFORE the value (the colon |
| // when tokenOffset==0, or the last whitespace character otherwise). |
| // The former `if data[i] == '{'` block-skip was structurally dead |
| // code because i never reaches the opening brace — the outer loop's |
| // i++ advances to it on the next iteration. Likewise, the former |
| // `if i < ln` guard was tautological since i remains within bounds. |
| } |
| |
| switch data[i] { |
| case '{', '}', '[', '"': |
| i-- |
| } |
| } else { |
| i-- |
| } |
| case '{': |
| level++ |
| case '}': |
| level-- |
| case '[': |
| var ok bool |
| arrIdxFlags := make(map[int]struct{}) |
| |
| pIdxFlags := make([]bool, stackArraySize)[:] |
| if len(paths) > cap(pIdxFlags) { |
| pIdxFlags = make([]bool, len(paths))[:] |
| } |
| pIdxFlags = pIdxFlags[0: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 |
| ) |
| |
| // SYS-REQ-001 |
| 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") |
| ) |
| |
| // SYS-REQ-009 |
| 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 |
| } |
| |
| // SYS-REQ-009 |
| 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 |
| } |
| |
| // SYS-REQ-009 |
| 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 |
| |
| */ |
| // SYS-REQ-010, SYS-REQ-033, SYS-REQ-034, SYS-REQ-035, SYS-REQ-048, SYS-REQ-049, SYS-REQ-050, SYS-REQ-056 |
| 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 != nil { |
| // 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 |
| var subEndOffset int |
| _, _, _, subEndOffset, err = internalGet(data[startOffset:endOffset], keys[lk-1]) |
| if err != nil { |
| return data |
| } |
| endOffset = startOffset + subEndOffset |
| tokEnd := tokenEnd(data[endOffset:]) |
| tokStart := findTokenStart(data[:keyOffset], ","[0]) |
| |
| if endOffset+tokEnd >= len(data) { |
| // tokenEnd sentinel: no delimiter found, input is truncated |
| return data |
| } |
| |
| if data[endOffset+tokEnd] == ',' { |
| endOffset += tokEnd + 1 |
| } else if data[endOffset+tokEnd] == ' ' && len(data) > endOffset+tokEnd+1 && data[endOffset+tokEnd+1] == ',' { |
| endOffset += tokEnd + 2 |
| } else if data[endOffset+tokEnd] == '}' && data[tokStart] == ',' { |
| keyOffset = tokStart |
| } |
| } else { |
| _, _, keyOffset, endOffset, err = internalGet(data, keys...) |
| if err != nil { |
| // problem parsing the data |
| return data |
| } |
| |
| tokEnd := tokenEnd(data[endOffset:]) |
| tokStart := findTokenStart(data[:keyOffset], ","[0]) |
| |
| if endOffset+tokEnd >= len(data) { |
| // tokenEnd sentinel: no delimiter found, input is truncated |
| return data |
| } |
| |
| if data[endOffset+tokEnd] == ',' { |
| endOffset += tokEnd + 1 |
| } else if data[endOffset+tokEnd] == ']' && data[tokStart] == ',' { |
| keyOffset = tokStart |
| } |
| } |
| |
| // We need to remove remaining trailing comma if we delete last element in the object. |
| // Extract nextToken once to avoid the redundant double call in the original code. |
| prevTok := lastToken(data[:keyOffset]) |
| remainedValue := data[endOffset:] |
| remainedTok := nextToken(remainedValue) |
| |
| var newOffset int |
| if remainedTok > -1 && remainedValue[remainedTok] == '}' && 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 |
| |
| */ |
| // SYS-REQ-009, SYS-REQ-051, SYS-REQ-068, SYS-REQ-069, SYS-REQ-070 |
| 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 |
| } |
| |
| // SYS-REQ-001, SYS-REQ-027 |
| 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 |
| // tokenEnd returns len(data) when no delimiter is found, never -1, |
| // so the old end == -1 guard was dead code and has been removed. |
| end := tokenEnd(data[endOffset:]) |
| |
| 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. |
| */ |
| // SYS-REQ-001, SYS-REQ-016, SYS-REQ-017, SYS-REQ-018, SYS-REQ-019, SYS-REQ-025, SYS-REQ-026, SYS-REQ-041, SYS-REQ-042, SYS-REQ-043 |
| 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 |
| } |
| |
| // SYS-REQ-001 |
| 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 |
| } |
| |
| // SYS-REQ-006, SYS-REQ-028, SYS-REQ-029, SYS-REQ-052, SYS-REQ-053, SYS-REQ-055, SYS-REQ-083 |
| // 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 { |
| v, t, o, e := Get(data[offset:]) |
| |
| if o == 0 { |
| // When Get returns endOffset==0, it always means a parse error |
| // (no valid value found at the current position). The former |
| // e==nil/break branch was structurally unreachable because Get |
| // never returns endOffset==0 without an error. |
| return offset, e |
| } |
| |
| // Pass the error to the callback — the callback signature declares |
| // an err parameter, so callers who check it should see real errors. |
| cb(v, t, offset+o-len(v), e) |
| |
| if e != nil { |
| return offset, e |
| } |
| |
| 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 |
| } |
| |
| // SYS-REQ-007, SYS-REQ-030, SYS-REQ-031, SYS-REQ-032, SYS-REQ-054, SYS-REQ-084 |
| // 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). |
| // Every iteration either returns or advances offset past a token, so the loop |
| // always exits via return; the former `offset < len(data)` guard was structurally |
| // always true because internal nextToken/stringEnd calls return errors before |
| // offset can reach len(data). |
| for { |
| // 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 |
| } |
| |
| // SYS-REQ-011, SYS-REQ-080, SYS-REQ-081, SYS-REQ-082 |
| // 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 |
| } |
| |
| // SYS-REQ-002, SYS-REQ-071, SYS-REQ-072, SYS-REQ-073, SYS-REQ-074 |
| // 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 { |
| if t == Null { |
| return "", NullValueError |
| } |
| 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. |
| // SYS-REQ-004 |
| 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 { |
| if t == Null { |
| return 0, NullValueError |
| } |
| 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. |
| // SYS-REQ-003, SYS-REQ-075, SYS-REQ-076, SYS-REQ-077, SYS-REQ-078 |
| 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 { |
| if t == Null { |
| return 0, NullValueError |
| } |
| 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. |
| // SYS-REQ-005, SYS-REQ-079 |
| 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 { |
| if t == Null { |
| return false, NullValueError |
| } |
| 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) |
| // SYS-REQ-012, SYS-REQ-036, SYS-REQ-057, SYS-REQ-066 |
| 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) |
| // SYS-REQ-014, SYS-REQ-038, SYS-REQ-060, SYS-REQ-063, SYS-REQ-067 |
| 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 |
| // SYS-REQ-013, SYS-REQ-037, SYS-REQ-065 |
| 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 |
| // SYS-REQ-015, SYS-REQ-039, SYS-REQ-040, SYS-REQ-058, SYS-REQ-059, SYS-REQ-064 |
| 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 |
| } |
| } |