123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191 |
- package jsonparser
- import (
- "bytes"
- "errors"
- "fmt"
- "math"
- "strconv"
- )
- // Errors
- var (
- KeyPathNotFoundError = errors.New("Key path not found")
- UnknownValueTypeError = errors.New("Unknown value type")
- MalformedJsonError = errors.New("Malformed JSON error")
- MalformedStringError = errors.New("Value is string, but can't find closing '\"' symbol")
- MalformedArrayError = errors.New("Value is array, but can't find closing ']' symbol")
- MalformedObjectError = errors.New("Value looks like object, but can't find closing '}' symbol")
- MalformedValueError = errors.New("Value looks like Number/Boolean/None, but can't find its end: ',' or '}' symbol")
- MalformedStringEscapeError = errors.New("Encountered an invalid escape sequence in a string")
- )
- // How much stack space to allocate for unescaping JSON strings; if a string longer
- // than this needs to be escaped, it will result in a heap allocation
- const unescapeStackBufSize = 64
- func tokenEnd(data []byte) int {
- for i, c := range data {
- switch c {
- case ' ', '\n', '\r', '\t', ',', '}', ']':
- return i
- }
- }
- return len(data)
- }
- 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 '[':
- i = blockEnd(data[i:], data[i], ']') + i
- case '{':
- i = blockEnd(data[i:], data[i], '}') + i
- }
- 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)
- if lk == 0 {
- return 0
- }
- var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings
- for i < ln {
- switch data[i] {
- case '"':
- i++
- keyBegin := i
- strEnd, keyEscaped := stringEnd(data[i:])
- if strEnd == -1 {
- return -1
- }
- i += strEnd
- keyEnd := i - 1
- valueOffset := nextToken(data[i:])
- if valueOffset == -1 {
- return -1
- }
- i += valueOffset
- // if string is a key, and key level match
- if data[i] == ':' && keyLevel == level-1 {
- 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 equalStr(&keyUnesc, keys[level-1]) {
- keyLevel++
- // If we found all keys in path
- if keyLevel == lk {
- return i + 1
- }
- }
- } else {
- i--
- }
- case '{':
- level++
- case '}':
- level--
- if level == keyLevel {
- keyLevel--
- }
- case '[':
- // If we want to get array element by index
- if keyLevel == level && keys[level][0] == '[' {
- aIdx, err := strconv.Atoi(keys[level][1 : len(keys[level])-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
- }
- }
- }
- i++
- }
- return -1
- }
- var bitwiseFlags []int64
- func init() {
- for i := 0; i < 63; i++ {
- bitwiseFlags = append(bitwiseFlags, int64(math.Pow(2, float64(i))))
- }
- }
- func sameTree(p1, p2 []string) bool {
- minLen := len(p1)
- if len(p2) < minLen {
- minLen = len(p2)
- }
- for pi_1, p_1 := range p1[:minLen] {
- if p2[pi_1] != p_1 {
- return false
- }
- }
- return true
- }
- func EachKey(data []byte, cb func(int, []byte, ValueType, error), paths ...[]string) int {
- var pathFlags int64
- var level, pathsMatched, i int
- ln := len(data)
- var maxPath int
- for _, p := range paths {
- if len(p) > maxPath {
- maxPath = len(p)
- }
- }
- var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings
- pathsBuf := make([]string, maxPath)
- for i < ln {
- switch data[i] {
- case '"':
- i++
- keyBegin := i
- strEnd, keyEscaped := stringEnd(data[i:])
- if strEnd == -1 {
- return -1
- }
- i += strEnd
- keyEnd := i - 1
- valueOffset := nextToken(data[i:])
- if valueOffset == -1 {
- return -1
- }
- i += valueOffset
- // if string is a key, and key level match
- if data[i] == ':' {
- match := -1
- key := data[keyBegin:keyEnd]
- // for unescape: if there are no escape sequences, this is cheap; if there are, it is a
- // bit more expensive, but causes no allocations unless len(key) > unescapeStackBufSize
- var keyUnesc []byte
- if !keyEscaped {
- keyUnesc = key
- } else if ku, err := Unescape(key, stackbuf[:]); err != nil {
- return -1
- } else {
- keyUnesc = ku
- }
- if maxPath >= level {
- 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&bitwiseFlags[pi+1] != 0 || !equalStr(&keyUnesc, p[level-1]) || !sameTree(p, pathsBuf[:level]) {
- continue
- }
- match = pi
- i++
- pathsMatched++
- pathFlags |= bitwiseFlags[pi+1]
- v, dt, of, e := Get(data[i:])
- cb(pi, v, dt, e)
- if of != -1 {
- i += of
- }
- if pathsMatched == len(paths) {
- 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 arrIdxFlags int64
- var pIdxFlags int64
- if level < 0 {
- cb(-1, nil, Unknown, MalformedJsonError)
- return -1
- }
- for pi, p := range paths {
- if len(p) < level+1 || pathFlags&bitwiseFlags[pi+1] != 0 || p[level][0] != '[' || !sameTree(p, pathsBuf[:level]) {
- continue
- }
- aIdx, _ := strconv.Atoi(p[level][1 : len(p[level])-1])
- arrIdxFlags |= bitwiseFlags[aIdx+1]
- pIdxFlags |= bitwiseFlags[pi+1]
- }
- if arrIdxFlags > 0 {
- level++
- var curIdx int
- arrOff, _ := ArrayEach(data[i:], func(value []byte, dataType ValueType, offset int, err error) {
- if arrIdxFlags&bitwiseFlags[curIdx+1] != 0 {
- for pi, p := range paths {
- if pIdxFlags&bitwiseFlags[pi+1] != 0 {
- aIdx, _ := strconv.Atoi(p[level-1][1 : len(p[level-1])-1])
- if curIdx == aIdx {
- of := searchKeys(value, p[level:]...)
- pathsMatched++
- pathFlags |= bitwiseFlags[pi+1]
- if of != -1 {
- v, dt, _, e := Get(value[of:])
- cb(pi, v, dt, e)
- }
- }
- }
- }
- }
- curIdx += 1
- })
- if pathsMatched == len(paths) {
- return i
- }
- i += arrOff - 1
- } else {
- // Do not search for keys inside arrays
- if arraySkip := blockEnd(data[i:], '[', ']'); arraySkip == -1 {
- return -1
- } else {
- i += arraySkip - 1
- }
- }
- case ']':
- level--
- }
- i++
- }
- return -1
- }
- // Data types available in valid JSON data.
- type ValueType int
- const (
- NotExist = ValueType(iota)
- String
- Number
- Object
- Array
- Boolean
- Null
- Unknown
- )
- func (vt ValueType) String() string {
- switch vt {
- case NotExist:
- return "non-existent"
- case String:
- return "string"
- case Number:
- return "number"
- case Object:
- return "object"
- case Array:
- return "array"
- case Boolean:
- return "boolean"
- case Null:
- return "null"
- default:
- return "unknown"
- }
- }
- var (
- trueLiteral = []byte("true")
- falseLiteral = []byte("false")
- nullLiteral = []byte("null")
- )
- func createInsertComponent(keys []string, setValue []byte, comma, object bool) []byte {
- var buffer bytes.Buffer
- isIndex := string(keys[0][0]) == "["
- if comma {
- buffer.WriteString(",")
- }
- if isIndex {
- buffer.WriteString("[")
- } else {
- if object {
- buffer.WriteString("{")
- }
- buffer.WriteString("\"")
- buffer.WriteString(keys[0])
- buffer.WriteString("\":")
- }
- for i := 1; i < len(keys); i++ {
- if string(keys[i][0]) == "[" {
- buffer.WriteString("[")
- } else {
- buffer.WriteString("{\"")
- buffer.WriteString(keys[i])
- buffer.WriteString("\":")
- }
- }
- buffer.Write(setValue)
- for i := len(keys) - 1; i > 0; i-- {
- if string(keys[i][0]) == "[" {
- buffer.WriteString("]")
- } else {
- buffer.WriteString("}")
- }
- }
- if isIndex {
- buffer.WriteString("]")
- }
- if object && !isIndex {
- buffer.WriteString("}")
- }
- return buffer.Bytes()
- }
- /*
- 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
- }
- data = append(data[:newOffset], data[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 len(data) == 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
- if data[startOffset] == '{' && data[startOffset+1+nextToken(data[startOffset+1:])] != '}' {
- 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, 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
- }
- offset = 1
- if len(keys) > 0 {
- if offset = searchKeys(data, keys...); offset == -1 {
- return offset, KeyPathNotFoundError
- }
- // Go to closest value
- nO := nextToken(data[offset:])
- if nO == -1 {
- return offset, MalformedJsonError
- }
- offset += nO
- if data[offset] != '[' {
- return offset, MalformedArrayError
- }
- offset++
- }
- nO := nextToken(data[offset:])
- if nO == -1 {
- return offset, MalformedJsonError
- }
- offset += nO
- if data[offset] == ']' {
- return offset, nil
- }
- for true {
- v, t, o, e := Get(data[offset:])
- if e != nil {
- return offset, e
- }
- if o == 0 {
- break
- }
- if t != NotExist {
- cb(v, t, offset+o-len(v), e)
- }
- if e != nil {
- break
- }
- offset += o
- skipToToken := nextToken(data[offset:])
- if skipToToken == -1 {
- return offset, MalformedArrayError
- }
- offset += skipToToken
- if data[offset] == ']' {
- break
- }
- if data[offset] != ',' {
- return offset, MalformedArrayError
- }
- offset++
- }
- return offset, nil
- }
- // ObjectEach iterates over the key-value pairs of a JSON object, invoking a given callback for each such entry
- func ObjectEach(data []byte, callback func(key []byte, value []byte, dataType ValueType, offset int) error, keys ...string) (err error) {
- var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings
- offset := 0
- // Descend to the desired key, if requested
- if len(keys) > 0 {
- if off := searchKeys(data, keys...); off == -1 {
- return KeyPathNotFoundError
- } else {
- offset = off
- }
- }
- // Validate and skip past opening brace
- if off := nextToken(data[offset:]); off == -1 {
- return MalformedObjectError
- } else if offset += off; data[offset] != '{' {
- return MalformedObjectError
- } else {
- offset++
- }
- // Skip to the first token inside the object, or stop if we find the ending brace
- if off := nextToken(data[offset:]); off == -1 {
- return MalformedJsonError
- } else if offset += off; data[offset] == '}' {
- return nil
- }
- // Loop pre-condition: data[offset] points to what should be either the next entry's key, or the closing brace (if it's anything else, the JSON is malformed)
- for offset < len(data) {
- // Step 1: find the next key
- var key []byte
- // Check what the the next token is: start of string, end of object, or something else (error)
- switch data[offset] {
- case '"':
- offset++ // accept as string and skip opening quote
- case '}':
- return nil // we found the end of the object; stop and return success
- default:
- return MalformedObjectError
- }
- // Find the end of the key string
- var keyEscaped bool
- if off, esc := stringEnd(data[offset:]); off == -1 {
- return MalformedJsonError
- } else {
- key, keyEscaped = data[offset:offset+off-1], esc
- offset += off
- }
- // Unescape the string if needed
- if keyEscaped {
- if keyUnescaped, err := Unescape(key, stackbuf[:]); err != nil {
- return MalformedStringEscapeError
- } else {
- key = keyUnescaped
- }
- }
- // Step 2: skip the colon
- if off := nextToken(data[offset:]); off == -1 {
- return MalformedJsonError
- } else if offset += off; data[offset] != ':' {
- return MalformedJsonError
- } else {
- offset++
- }
- // Step 3: find the associated value, then invoke the callback
- if value, valueType, off, err := Get(data[offset:]); err != nil {
- return err
- } else if err := callback(key, value, valueType, offset+off); err != nil { // Invoke the callback here!
- return err
- } else {
- offset += off
- }
- // Step 4: skip over the next comma to the following token, or stop if we hit the ending brace
- if off := nextToken(data[offset:]); off == -1 {
- return MalformedArrayError
- } else {
- offset += off
- switch data[offset] {
- case '}':
- return nil // Stop if we hit the close brace
- case ',':
- offset++ // Ignore the comma
- default:
- return MalformedObjectError
- }
- }
- // Skip to the next token after the comma
- if off := nextToken(data[offset:]); off == -1 {
- return MalformedArrayError
- } else {
- offset += off
- }
- }
- return MalformedObjectError // we shouldn't get here; it's expected that we will return via finding the ending brace
- }
- // GetUnsafeString returns the value retrieved by `Get`, use creates string without memory allocation by mapping string to slice memory. It does not handle escape symbols.
- func GetUnsafeString(data []byte, keys ...string) (val string, err error) {
- v, _, _, e := Get(data, keys...)
- if e != nil {
- return "", e
- }
- return bytesToString(&v), nil
- }
- // GetString returns the value retrieved by `Get`, cast to a string if possible, trying to properly handle escape and utf8 symbols
- // If key data type do not match, it will return an error.
- func GetString(data []byte, keys ...string) (val string, err error) {
- v, t, _, e := Get(data, keys...)
- if e != nil {
- return "", e
- }
- if t != String {
- return "", fmt.Errorf("Value is not a string: %s", string(v))
- }
- // If no escapes return raw conten
- if bytes.IndexByte(v, '\\') == -1 {
- return string(v), nil
- }
- return ParseString(v)
- }
- // GetFloat returns the value retrieved by `Get`, cast to a float64 if possible.
- // The offset is the same as in `Get`.
- // If key data type do not match, it will return an error.
- func GetFloat(data []byte, keys ...string) (val float64, err error) {
- v, t, _, e := Get(data, keys...)
- if e != nil {
- return 0, e
- }
- if t != Number {
- return 0, fmt.Errorf("Value is not a number: %s", string(v))
- }
- return ParseFloat(v)
- }
- // GetInt returns the value retrieved by `Get`, cast to a int64 if possible.
- // If key data type do not match, it will return an error.
- func GetInt(data []byte, keys ...string) (val int64, err error) {
- v, t, _, e := Get(data, keys...)
- if e != nil {
- return 0, e
- }
- if t != Number {
- return 0, fmt.Errorf("Value is not a number: %s", string(v))
- }
- return ParseInt(v)
- }
- // GetBoolean returns the value retrieved by `Get`, cast to a bool if possible.
- // The offset is the same as in `Get`.
- // If key data type do not match, it will return error.
- func GetBoolean(data []byte, keys ...string) (val bool, err error) {
- v, t, _, e := Get(data, keys...)
- if e != nil {
- return false, e
- }
- if t != Boolean {
- return false, fmt.Errorf("Value is not a boolean: %s", string(v))
- }
- return ParseBoolean(v)
- }
- // ParseBoolean parses a Boolean ValueType into a Go bool (not particularly useful, but here for completeness)
- func ParseBoolean(b []byte) (bool, error) {
- switch {
- case bytes.Equal(b, trueLiteral):
- return true, nil
- case bytes.Equal(b, falseLiteral):
- return false, nil
- default:
- return false, MalformedValueError
- }
- }
- // ParseString parses a String ValueType into a Go string (the main parsing work is unescaping the JSON string)
- func ParseString(b []byte) (string, error) {
- var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings
- if bU, err := Unescape(b, stackbuf[:]); err != nil {
- return "", 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 := parseInt(b); !ok {
- return 0, MalformedValueError
- } else {
- return v, nil
- }
- }
|