12 KeyPathNotFoundError = errors.New("Key path not found")
13 UnknownValueTypeError = errors.New("Unknown value type")
14 MalformedJsonError = errors.New("Malformed JSON error")
15 MalformedStringError = errors.New("Value is string, but can't find closing '\"' symbol")
16 MalformedArrayError = errors.New("Value is array, but can't find closing ']' symbol")
17 MalformedObjectError = errors.New("Value looks like object, but can't find closing '}' symbol")
18 MalformedValueError = errors.New("Value looks like Number/Boolean/None, but can't find its end: ',' or '}' symbol")
19 OverflowIntegerError = errors.New("Value is number, but overflowed while parsing")
20 MalformedStringEscapeError = errors.New("Encountered an invalid escape sequence in a string")
23// How much stack space to allocate for unescaping JSON strings; if a string longer
24// than this needs to be escaped, it will result in a heap allocation
25const unescapeStackBufSize = 64
27func tokenEnd(data []byte) int {
28 for i, c := range data {
30 case ' ', '\n', '\r', '\t', ',', '}', ']':
38func findTokenStart(data []byte, token byte) int {
39 for i := len(data) - 1; i >= 0; i-- {
51func findKeyStart(data []byte, key string) (int, error) {
54 if ln > 0 && (data[0] == '{' || data[0] == '[') {
57 var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings
59 if ku, err := Unescape(StringToBytes(key), stackbuf[:]); err == nil {
60 key = bytesToString(&ku)
69 strEnd, keyEscaped := stringEnd(data[i:])
76 valueOffset := nextToken(data[i:])
77 if valueOffset == -1 {
83 // if string is a key, and key level match
84 k := data[keyBegin:keyEnd]
85 // for unescape: if there are no escape sequences, this is cheap; if there are, it is a
86 // bit more expensive, but causes no allocations unless len(key) > unescapeStackBufSize
88 if ku, err := Unescape(k, stackbuf[:]); err != nil {
95 if data[i] == ':' && len(key) == len(k) && bytesToString(&k) == key {
96 return keyBegin - 1, nil
100 end := blockEnd(data[i:], data[i], ']')
105 end := blockEnd(data[i:], data[i], '}')
113 return -1, KeyPathNotFoundError
116func tokenStart(data []byte) int {
117 for i := len(data) - 1; i >= 0; i-- {
119 case '\n', '\r', '\t', ',', '{', '[':
127// Find position of next character which is not whitespace
128func nextToken(data []byte) int {
129 for i, c := range data {
131 case ' ', '\n', '\r', '\t':
141// Find position of last character which is not whitespace
142func lastToken(data []byte) int {
143 for i := len(data) - 1; i >= 0; i-- {
145 case ' ', '\n', '\r', '\t':
155// Tries to find the end of string
156// Support if string contains escaped quote symbols.
157func stringEnd(data []byte) (int, bool) {
159 for i, c := range data {
166 if j < 0 || data[j] != '\\' {
167 return i + 1, true // even number of backslashes
170 if j < 0 || data[j] != '\\' {
171 break // odd number of backslashes
177 } else if c == '\\' {
185// Find end of the data structure, array or object.
186// For array openSym and closeSym will be '[' and ']', for object '{' and '}'
187func blockEnd(data []byte, openSym byte, closeSym byte) int {
194 case '"': // If inside string, skip it
195 se, _ := stringEnd(data[i+1:])
200 case openSym: // If open symbol, increase level
202 case closeSym: // If close symbol, increase level
205 // If we have returned to the original level, we're done
216func searchKeys(data []byte, keys ...string) int {
228 var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings
236 strEnd, keyEscaped := stringEnd(data[i:])
243 valueOffset := nextToken(data[i:])
244 if valueOffset == -1 {
250 // if string is a key
256 key := data[keyBegin:keyEnd]
258 // for unescape: if there are no escape sequences, this is cheap; if there are, it is a
259 // bit more expensive, but causes no allocations unless len(key) > unescapeStackBufSize
263 } else if ku, err := Unescape(key, stackbuf[:]); err != nil {
269 if level <= len(keys) {
270 if equalStr(&keyUnesc, keys[level-1]) {
273 // if key level match
274 if keyLevel == level-1 {
276 // If we found all keys in path
292 // in case parent key is matched then only we will increase the level otherwise can directly
293 // can move to the end of this block
295 end := blockEnd(data[i:], '{', '}')
305 if level == keyLevel {
309 // If we want to get array element by index
310 if keyLevel == level && keys[level][0] == '[' {
311 var keyLen = len(keys[level])
312 if keyLen < 3 || keys[level][0] != '[' || keys[level][keyLen-1] != ']' {
315 aIdx, err := strconv.Atoi(keys[level][1 : keyLen-1])
320 var valueFound []byte
323 ArrayEach(data[i:], func(value []byte, dataType ValueType, offset int, err error) {
327 if dataType == String {
328 valueOffset = valueOffset - 2
329 valueFound = data[curI+valueOffset : curI+valueOffset+len(value)+2]
335 if valueFound == nil {
338 subIndex := searchKeys(valueFound, keys[level+1:]...)
342 return i + valueOffset + subIndex
345 // Do not search for keys inside arrays
346 if arraySkip := blockEnd(data[i:], '[', ']'); arraySkip == -1 {
352 case ':': // If encountered, JSON data is malformed
362func sameTree(p1, p2 []string) bool {
364 if len(p2) < minLen {
368 for pi_1, p_1 := range p1[:minLen] {
377func EachKey(data []byte, cb func(int, []byte, ValueType, error), paths ...[]string) int {
379 pathFlags := make([]bool, len(paths))
380 var level, pathsMatched, i int
384 for _, p := range paths {
385 if len(p) > maxPath {
390 pathsBuf := make([]string, maxPath)
398 strEnd, keyEscaped := stringEnd(data[i:])
406 valueOffset := nextToken(data[i:])
407 if valueOffset == -1 {
413 // if string is a key, and key level match
416 key := data[keyBegin:keyEnd]
418 // for unescape: if there are no escape sequences, this is cheap; if there are, it is a
419 // bit more expensive, but causes no allocations unless len(key) > unescapeStackBufSize
424 var stackbuf [unescapeStackBufSize]byte
425 if ku, err := Unescape(key, stackbuf[:]); err != nil {
432 if maxPath >= level {
434 cb(-1, nil, Unknown, MalformedJsonError)
438 pathsBuf[level-1] = bytesToString(&keyUnesc)
439 for pi, p := range paths {
440 if len(p) != level || pathFlags[pi] || !equalStr(&keyUnesc, p[level-1]) || !sameTree(p, pathsBuf[:level]) {
449 v, dt, _, e := Get(data[i+1:])
452 if pathsMatched == len(paths) {
456 if pathsMatched == len(paths) {
462 tokenOffset := nextToken(data[i+1:])
466 blockSkip := blockEnd(data[i:], '{', '}')
473 case '{', '}', '[', '"':
486 arrIdxFlags := make(map[int]struct{})
487 pIdxFlags := make([]bool, len(paths))
490 cb(-1, nil, Unknown, MalformedJsonError)
494 for pi, p := range paths {
495 if len(p) < level+1 || pathFlags[pi] || p[level][0] != '[' || !sameTree(p, pathsBuf[:level]) {
498 if len(p[level]) >= 2 {
499 aIdx, _ := strconv.Atoi(p[level][1 : len(p[level])-1])
500 arrIdxFlags[aIdx] = x
505 if len(arrIdxFlags) > 0 {
509 arrOff, _ := ArrayEach(data[i:], func(value []byte, dataType ValueType, offset int, err error) {
510 if _, ok = arrIdxFlags[curIdx]; ok {
511 for pi, p := range paths {
513 aIdx, _ := strconv.Atoi(p[level-1][1 : len(p[level-1])-1])
516 of := searchKeys(value, p[level:]...)
522 v, dt, _, e := Get(value[of:])
533 if pathsMatched == len(paths) {
539 // Do not search for keys inside arrays
540 if arraySkip := blockEnd(data[i:], '[', ']'); arraySkip == -1 {
556// Data types available in valid JSON data.
560 NotExist = ValueType(iota)
570func (vt ValueType) String() string {
573 return "non-existent"
592 trueLiteral = []byte("true")
593 falseLiteral = []byte("false")
594 nullLiteral = []byte("null")
597func createInsertComponent(keys []string, setValue []byte, comma, object bool) []byte {
598 isIndex := string(keys[0][0]) == "["
600 lk := calcAllocateSpace(keys, setValue, comma, object)
601 buffer := make([]byte, lk, lk)
603 offset += WriteToBuffer(buffer[offset:], ",")
605 if isIndex && !comma {
606 offset += WriteToBuffer(buffer[offset:], "[")
609 offset += WriteToBuffer(buffer[offset:], "{")
612 offset += WriteToBuffer(buffer[offset:], "\"")
613 offset += WriteToBuffer(buffer[offset:], keys[0])
614 offset += WriteToBuffer(buffer[offset:], "\":")
618 for i := 1; i < len(keys); i++ {
619 if string(keys[i][0]) == "[" {
620 offset += WriteToBuffer(buffer[offset:], "[")
622 offset += WriteToBuffer(buffer[offset:], "{\"")
623 offset += WriteToBuffer(buffer[offset:], keys[i])
624 offset += WriteToBuffer(buffer[offset:], "\":")
627 offset += WriteToBuffer(buffer[offset:], string(setValue))
628 for i := len(keys) - 1; i > 0; i-- {
629 if string(keys[i][0]) == "[" {
630 offset += WriteToBuffer(buffer[offset:], "]")
632 offset += WriteToBuffer(buffer[offset:], "}")
635 if isIndex && !comma {
636 offset += WriteToBuffer(buffer[offset:], "]")
638 if object && !isIndex {
639 offset += WriteToBuffer(buffer[offset:], "}")
644func calcAllocateSpace(keys []string, setValue []byte, comma, object bool) int {
645 isIndex := string(keys[0][0]) == "["
651 if isIndex && !comma {
661 lk += len(keys[0]) + 3
667 for i := 1; i < len(keys); i++ {
668 if string(keys[i][0]) == "[" {
672 // {"keys[i]":setValue}
673 lk += len(keys[i]) + 5
677 if object && !isIndex {
685func WriteToBuffer(buffer []byte, str string) int {
692Del - Receives existing data structure, path to delete.
695`data` - return modified data
698func Delete(data []byte, keys ...string) []byte {
705 if len(keys[lk-1]) > 0 && string(keys[lk-1][0]) == "[" {
709 var startOffset, keyOffset int
710 endOffset := len(data)
714 _, _, startOffset, endOffset, err = internalGet(data, keys[:lk-1]...)
715 if err == KeyPathNotFoundError {
716 // problem parsing the data
721 keyOffset, err = findKeyStart(data[startOffset:endOffset], keys[lk-1])
722 if err == KeyPathNotFoundError {
723 // problem parsing the data
726 keyOffset += startOffset
727 _, _, _, subEndOffset, _ := internalGet(data[startOffset:endOffset], keys[lk-1])
728 endOffset = startOffset + subEndOffset
729 tokEnd := tokenEnd(data[endOffset:])
730 tokStart := findTokenStart(data[:keyOffset], ","[0])
732 if data[endOffset+tokEnd] == ","[0] {
733 endOffset += tokEnd + 1
734 } else if data[endOffset+tokEnd] == " "[0] && len(data) > endOffset+tokEnd+1 && data[endOffset+tokEnd+1] == ","[0] {
735 endOffset += tokEnd + 2
736 } else if data[endOffset+tokEnd] == "}"[0] && data[tokStart] == ","[0] {
740 _, _, keyOffset, endOffset, err = internalGet(data, keys...)
741 if err == KeyPathNotFoundError {
742 // problem parsing the data
746 tokEnd := tokenEnd(data[endOffset:])
747 tokStart := findTokenStart(data[:keyOffset], ","[0])
749 if data[endOffset+tokEnd] == ","[0] {
750 endOffset += tokEnd + 1
751 } else if data[endOffset+tokEnd] == "]"[0] && data[tokStart] == ","[0] {
756 // We need to remove remaining trailing comma if we delete las element in the object
757 prevTok := lastToken(data[:keyOffset])
758 remainedValue := data[endOffset:]
761 if nextToken(remainedValue) > -1 && remainedValue[nextToken(remainedValue)] == '}' && data[prevTok] == ',' {
764 newOffset = prevTok + 1
767 // We have to make a copy here if we don't want to mangle the original data, because byte slices are
768 // accessed by reference and not by value
769 dataCopy := make([]byte, len(data))
771 data = append(dataCopy[:newOffset], dataCopy[endOffset:]...)
778Set - Receives existing data structure, path to set, and data to set at that key.
781`value` - modified byte array
782`err` - On any parsing error
785func Set(data []byte, setValue []byte, keys ...string) (value []byte, err error) {
786 // ensure keys are set
788 return nil, KeyPathNotFoundError
791 _, _, startOffset, endOffset, err := internalGet(data, keys...)
793 if err != KeyPathNotFoundError {
794 // problem parsing the data
797 // full path doesnt exist
798 // does any subpath exist?
800 for i := range keys {
801 _, _, start, end, sErr := internalGet(data, keys[:i+1]...)
813 firstToken := nextToken(data)
814 // We can't set a top-level key if data isn't an object
815 if firstToken < 0 || data[firstToken] != '{' {
816 return nil, KeyPathNotFoundError
818 // Don't need a comma if the input is an empty object
819 secondToken := firstToken + 1 + nextToken(data[firstToken+1:])
820 if data[secondToken] == '}' {
823 // Set the top level key at the end (accounting for any trailing whitespace)
824 // This assumes last token is valid like '}', could check and return error
825 endOffset = lastToken(data)
827 depthOffset := endOffset
829 // if subpath is a non-empty object, add to it
830 // or if subpath is a non-empty array, add to it
831 if (data[startOffset] == '{' && data[startOffset+1+nextToken(data[startOffset+1:])] != '}') ||
832 (data[startOffset] == '[' && data[startOffset+1+nextToken(data[startOffset+1:])] == '{') && keys[depth:][0][0] == 91 {
834 startOffset = depthOffset
835 // otherwise, over-write it with a new object
841 startOffset = depthOffset
843 value = append(data[:startOffset], append(createInsertComponent(keys[depth:], setValue, comma, object), data[depthOffset:]...)...)
845 // path currently exists
846 startComponent := data[:startOffset]
847 endComponent := data[endOffset:]
849 value = make([]byte, len(startComponent)+len(endComponent)+len(setValue))
850 newEndOffset := startOffset + len(setValue)
851 copy(value[0:startOffset], startComponent)
852 copy(value[startOffset:newEndOffset], setValue)
853 copy(value[newEndOffset:], endComponent)
858func getType(data []byte, offset int) ([]byte, ValueType, int, error) {
859 var dataType ValueType
863 if data[offset] == '"' {
865 if idx, _ := stringEnd(data[offset+1:]); idx != -1 {
868 return nil, dataType, offset, MalformedStringError
870 } else if data[offset] == '[' { // if array value
872 // break label, for stopping nested loops
873 endOffset = blockEnd(data[offset:], '[', ']')
876 return nil, dataType, offset, MalformedArrayError
880 } else if data[offset] == '{' { // if object value
882 // break label, for stopping nested loops
883 endOffset = blockEnd(data[offset:], '{', '}')
886 return nil, dataType, offset, MalformedObjectError
891 // Number, Boolean or None
892 end := tokenEnd(data[endOffset:])
895 return nil, dataType, offset, MalformedValueError
898 value := data[offset : endOffset+end]
900 switch data[offset] {
901 case 't', 'f': // true or false
902 if bytes.Equal(value, trueLiteral) || bytes.Equal(value, falseLiteral) {
905 return nil, Unknown, offset, UnknownValueTypeError
907 case 'u', 'n': // undefined or null
908 if bytes.Equal(value, nullLiteral) {
911 return nil, Unknown, offset, UnknownValueTypeError
913 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-':
916 return nil, Unknown, offset, UnknownValueTypeError
921 return data[offset:endOffset], dataType, endOffset, nil
925Get - Receives data structure, and key path to extract value from.
928`value` - Pointer to original data structure containing key value, or just empty slice if nothing found or error
929`dataType` - Can be: `NotExist`, `String`, `Number`, `Object`, `Array`, `Boolean` or `Null`
930`offset` - Offset from provided data structure where key value ends. Used mostly internally, for example for `ArrayEach` helper.
931`err` - If key not found or any other parsing issue it should return error. If key not found it also sets `dataType` to `NotExist`
933Accept multiple keys to specify path to JSON value (in case of quering nested structures).
934If 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.
936func Get(data []byte, keys ...string) (value []byte, dataType ValueType, offset int, err error) {
937 a, b, _, d, e := internalGet(data, keys...)
941func internalGet(data []byte, keys ...string) (value []byte, dataType ValueType, offset, endOffset int, err error) {
943 if offset = searchKeys(data, keys...); offset == -1 {
944 return nil, NotExist, -1, -1, KeyPathNotFoundError
948 // Go to closest value
949 nO := nextToken(data[offset:])
951 return nil, NotExist, offset, -1, MalformedJsonError
955 value, dataType, endOffset, err = getType(data, offset)
957 return value, dataType, offset, endOffset, err
960 // Strip quotes from string values
961 if dataType == String {
962 value = value[1 : len(value)-1]
965 return value[:len(value):len(value)], dataType, offset, endOffset, nil
968// ArrayEach is used when iterating arrays, accepts a callback function with the same return arguments as `Get`.
969func ArrayEach(data []byte, cb func(value []byte, dataType ValueType, offset int, err error), keys ...string) (offset int, err error) {
971 return -1, MalformedObjectError
974 nT := nextToken(data)
976 return -1, MalformedJsonError
982 if offset = searchKeys(data, keys...); offset == -1 {
983 return offset, KeyPathNotFoundError
986 // Go to closest value
987 nO := nextToken(data[offset:])
989 return offset, MalformedJsonError
994 if data[offset] != '[' {
995 return offset, MalformedArrayError
1001 nO := nextToken(data[offset:])
1003 return offset, MalformedJsonError
1008 if data[offset] == ']' {
1013 v, t, o, e := Get(data[offset:])
1024 cb(v, t, offset+o-len(v), e)
1033 skipToToken := nextToken(data[offset:])
1034 if skipToToken == -1 {
1035 return offset, MalformedArrayError
1037 offset += skipToToken
1039 if data[offset] == ']' {
1043 if data[offset] != ',' {
1044 return offset, MalformedArrayError
1053// ObjectEach iterates over the key-value pairs of a JSON object, invoking a given callback for each such entry
1054func ObjectEach(data []byte, callback func(key []byte, value []byte, dataType ValueType, offset int) error, keys ...string) (err error) {
1057 // Descend to the desired key, if requested
1059 if off := searchKeys(data, keys...); off == -1 {
1060 return KeyPathNotFoundError
1066 // Validate and skip past opening brace
1067 if off := nextToken(data[offset:]); off == -1 {
1068 return MalformedObjectError
1069 } else if offset += off; data[offset] != '{' {
1070 return MalformedObjectError
1075 // Skip to the first token inside the object, or stop if we find the ending brace
1076 if off := nextToken(data[offset:]); off == -1 {
1077 return MalformedJsonError
1078 } else if offset += off; data[offset] == '}' {
1082 // 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)
1083 for offset < len(data) {
1084 // Step 1: find the next key
1087 // Check what the the next token is: start of string, end of object, or something else (error)
1088 switch data[offset] {
1090 offset++ // accept as string and skip opening quote
1092 return nil // we found the end of the object; stop and return success
1094 return MalformedObjectError
1097 // Find the end of the key string
1099 if off, esc := stringEnd(data[offset:]); off == -1 {
1100 return MalformedJsonError
1102 key, keyEscaped = data[offset:offset+off-1], esc
1106 // Unescape the string if needed
1108 var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings
1109 if keyUnescaped, err := Unescape(key, stackbuf[:]); err != nil {
1110 return MalformedStringEscapeError
1116 // Step 2: skip the colon
1117 if off := nextToken(data[offset:]); off == -1 {
1118 return MalformedJsonError
1119 } else if offset += off; data[offset] != ':' {
1120 return MalformedJsonError
1125 // Step 3: find the associated value, then invoke the callback
1126 if value, valueType, off, err := Get(data[offset:]); err != nil {
1128 } else if err := callback(key, value, valueType, offset+off); err != nil { // Invoke the callback here!
1134 // Step 4: skip over the next comma to the following token, or stop if we hit the ending brace
1135 if off := nextToken(data[offset:]); off == -1 {
1136 return MalformedArrayError
1139 switch data[offset] {
1141 return nil // Stop if we hit the close brace
1143 offset++ // Ignore the comma
1145 return MalformedObjectError
1149 // Skip to the next token after the comma
1150 if off := nextToken(data[offset:]); off == -1 {
1151 return MalformedArrayError
1157 return MalformedObjectError // we shouldn't get here; it's expected that we will return via finding the ending brace
1160// 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.
1161func GetUnsafeString(data []byte, keys ...string) (val string, err error) {
1162 v, _, _, e := Get(data, keys...)
1168 return bytesToString(&v), nil
1171// GetString returns the value retrieved by `Get`, cast to a string if possible, trying to properly handle escape and utf8 symbols
1172// If key data type do not match, it will return an error.
1173func GetString(data []byte, keys ...string) (val string, err error) {
1174 v, t, _, e := Get(data, keys...)
1181 return "", fmt.Errorf("Value is not a string: %s", string(v))
1184 // If no escapes return raw content
1185 if bytes.IndexByte(v, '\\') == -1 {
1186 return string(v), nil
1189 return ParseString(v)
1192// GetFloat returns the value retrieved by `Get`, cast to a float64 if possible.
1193// The offset is the same as in `Get`.
1194// If key data type do not match, it will return an error.
1195func GetFloat(data []byte, keys ...string) (val float64, err error) {
1196 v, t, _, e := Get(data, keys...)
1203 return 0, fmt.Errorf("Value is not a number: %s", string(v))
1206 return ParseFloat(v)
1209// GetInt returns the value retrieved by `Get`, cast to a int64 if possible.
1210// If key data type do not match, it will return an error.
1211func GetInt(data []byte, keys ...string) (val int64, err error) {
1212 v, t, _, e := Get(data, keys...)
1219 return 0, fmt.Errorf("Value is not a number: %s", string(v))
1225// GetBoolean returns the value retrieved by `Get`, cast to a bool if possible.
1226// The offset is the same as in `Get`.
1227// If key data type do not match, it will return error.
1228func GetBoolean(data []byte, keys ...string) (val bool, err error) {
1229 v, t, _, e := Get(data, keys...)
1236 return false, fmt.Errorf("Value is not a boolean: %s", string(v))
1239 return ParseBoolean(v)
1242// ParseBoolean parses a Boolean ValueType into a Go bool (not particularly useful, but here for completeness)
1243func ParseBoolean(b []byte) (bool, error) {
1245 case bytes.Equal(b, trueLiteral):
1247 case bytes.Equal(b, falseLiteral):
1250 return false, MalformedValueError
1254// ParseString parses a String ValueType into a Go string (the main parsing work is unescaping the JSON string)
1255func ParseString(b []byte) (string, error) {
1256 var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings
1257 if bU, err := Unescape(b, stackbuf[:]); err != nil {
1258 return "", MalformedValueError
1260 return string(bU), nil
1264// ParseNumber parses a Number ValueType into a Go float64
1265func ParseFloat(b []byte) (float64, error) {
1266 if v, err := parseFloat(&b); err != nil {
1267 return 0, MalformedValueError
1273// ParseInt parses a Number ValueType into a Go int64
1274func ParseInt(b []byte) (int64, error) {
1275 if v, ok, overflow := parseInt(b); !ok {
1277 return 0, OverflowIntegerError
1279 return 0, MalformedValueError
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list