search.go raw

   1  /*
   2   * Copyright 2021 ByteDance Inc.
   3   *
   4   * Licensed under the Apache License, Version 2.0 (the "License");
   5   * you may not use this file except in compliance with the License.
   6   * You may obtain a copy of the License at
   7   *
   8   *     http://www.apache.org/licenses/LICENSE-2.0
   9   *
  10   * Unless required by applicable law or agreed to in writing, software
  11   * distributed under the License is distributed on an "AS IS" BASIS,
  12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13   * See the License for the specific language governing permissions and
  14   * limitations under the License.
  15   */
  16  
  17  package ast
  18  
  19  import (
  20      `github.com/bytedance/sonic/internal/rt`
  21      `github.com/bytedance/sonic/internal/native/types`
  22  )
  23  
  24  // SearchOptions controls Searcher's behavior
  25  type SearchOptions struct {
  26      // ValidateJSON indicates the searcher to validate the entire JSON
  27      ValidateJSON bool
  28  
  29      // CopyReturn indicates the searcher to copy the result JSON instead of refer from the input
  30      // This can help to reduce memory usage if you cache the results
  31      CopyReturn bool
  32  
  33      // ConcurrentRead indicates the searcher to return a concurrently-READ-safe node,
  34      // including: GetByPath/Get/Index/GetOrIndex/Int64/Bool/Float64/String/Number/Interface/Array/Map/Raw/MarshalJSON
  35      ConcurrentRead bool
  36  }
  37  
  38  type Searcher struct {
  39      parser Parser
  40      SearchOptions
  41  }
  42  
  43  func NewSearcher(str string) *Searcher {
  44      return &Searcher{
  45          parser: Parser{
  46              s:      str,
  47              noLazy: false,
  48          },
  49          SearchOptions: SearchOptions{
  50              ValidateJSON: true,
  51          },
  52      }
  53  }
  54  
  55  // GetByPathCopy search in depth from top json and returns a **Copied** json node at the path location
  56  func (self *Searcher) GetByPathCopy(path ...interface{}) (Node, error) {
  57      self.CopyReturn = true
  58      return self.getByPath(path...)
  59  }
  60  
  61  // GetByPathNoCopy search in depth from top json and returns a **Referenced** json node at the path location
  62  //
  63  // WARN: this search directly refer partial json from top json, which has faster speed,
  64  // may consumes more memory.
  65  func (self *Searcher) GetByPath(path ...interface{}) (Node, error) {
  66      return self.getByPath(path...)
  67  }
  68  
  69  func (self *Searcher) getByPath(path ...interface{}) (Node, error) {
  70      var err types.ParsingError
  71      var start int
  72  
  73      self.parser.p = 0
  74      start, err = self.parser.getByPath(self.ValidateJSON, path...)
  75      if err != 0 {
  76          // for compatibility with old version
  77          if err == types.ERR_NOT_FOUND {
  78              return Node{}, ErrNotExist
  79          }
  80          if err == types.ERR_UNSUPPORT_TYPE {
  81              panic("path must be either int(>=0) or string")
  82          }
  83          return Node{}, self.parser.syntaxError(err)
  84      }
  85  
  86      t := switchRawType(self.parser.s[start])
  87      if t == _V_NONE {
  88          return Node{}, self.parser.ExportError(err)
  89      }
  90  
  91      // copy string to reducing memory usage
  92      var raw string
  93      if self.CopyReturn {
  94          raw = rt.Mem2Str([]byte(self.parser.s[start:self.parser.p]))
  95      } else {
  96          raw = self.parser.s[start:self.parser.p]
  97      }
  98      return newRawNode(raw, t, self.ConcurrentRead), nil
  99  }
 100  
 101  // GetByPath searches a path and returns relaction and types of target
 102  func _GetByPath(src string, path ...interface{}) (start int, end int, typ int, err error) {
 103  	p := NewParserObj(src)
 104  	s, e := p.getByPath(false, path...)
 105  	if e != 0 {
 106  		// for compatibility with old version
 107  		if e == types.ERR_NOT_FOUND {
 108  			return -1, -1, 0, ErrNotExist
 109  		}
 110  		if e == types.ERR_UNSUPPORT_TYPE {
 111  			panic("path must be either int(>=0) or string")
 112  		}
 113  		return -1, -1, 0, p.syntaxError(e)
 114  	}
 115  
 116  	t := switchRawType(p.s[s])
 117  	if t == _V_NONE {
 118  		return -1, -1, 0, ErrNotExist
 119  	}
 120      if t == _V_NUMBER {
 121          p.p = 1 + backward(p.s, p.p-1)
 122      }
 123  	return s, p.p, int(t), nil
 124  }
 125  
 126  // ValidSyntax check if a json has a valid JSON syntax,
 127  // while not validate UTF-8 charset
 128  func _ValidSyntax(json string) bool {
 129  	p := NewParserObj(json)
 130      _, e := p.skip()
 131  	if e != 0 {
 132          return false
 133      }
 134     if skipBlank(p.s, p.p) != -int(types.ERR_EOF) {
 135          return false
 136     }
 137     return true
 138  }
 139  
 140  // SkipFast skip a json value in fast-skip algs, 
 141  // while not strictly validate JSON syntax and UTF-8 charset.
 142  func _SkipFast(src string, i int) (int, int, error) {
 143      p := NewParserObj(src)
 144      p.p = i
 145      s, e := p.skipFast()
 146      if e != 0 {
 147          return -1, -1, p.ExportError(e)
 148      }
 149      t := switchRawType(p.s[s])
 150  	if t == _V_NONE {
 151  		return -1, -1, ErrNotExist
 152  	}
 153      if t == _V_NUMBER {
 154          p.p = 1 + backward(p.s, p.p-1)
 155      }
 156      return s, p.p, nil
 157  }
 158