github.com/goshafaq/sonic@v0.0.0-20231026082336-871835fb94c6/README.md (about)

     1  # Sonic
     2  
     3  English | [中文](README_ZH_CN.md)
     4  
     5  A blazingly fast JSON serializing & deserializing library, accelerated by JIT (just-in-time compiling) and SIMD (single-instruction-multiple-data).
     6  
     7  ## Requirement
     8  - Go 1.16~1.21
     9  - Linux / MacOS / Windows(need go1.17 above)
    10  - Amd64 ARCH
    11  
    12  ## Features
    13  - Runtime object binding without code generation
    14  - Complete APIs for JSON value manipulation
    15  - Fast, fast, fast!
    16  
    17  ## Benchmarks
    18  For **all sizes** of json and **all scenarios** of usage, **Sonic performs best**.
    19  - [Medium](https://github.com/goshafaq/sonic/blob/main/decoder/testdata_test.go#L19) (13KB, 300+ key, 6 layers)
    20  ```powershell
    21  goversion: 1.17.1
    22  goos: darwin
    23  goarch: amd64
    24  cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
    25  BenchmarkEncoder_Generic_Sonic-16                      32393 ns/op         402.40 MB/s       11965 B/op          4 allocs/op
    26  BenchmarkEncoder_Generic_Sonic_Fast-16                 21668 ns/op         601.57 MB/s       10940 B/op          4 allocs/op
    27  BenchmarkEncoder_Generic_JsonIter-16                   42168 ns/op         309.12 MB/s       14345 B/op        115 allocs/op
    28  BenchmarkEncoder_Generic_GoJson-16                     65189 ns/op         199.96 MB/s       23261 B/op         16 allocs/op
    29  BenchmarkEncoder_Generic_StdLib-16                    106322 ns/op         122.60 MB/s       49136 B/op        789 allocs/op
    30  BenchmarkEncoder_Binding_Sonic-16                       6269 ns/op        2079.26 MB/s       14173 B/op          4 allocs/op
    31  BenchmarkEncoder_Binding_Sonic_Fast-16                  5281 ns/op        2468.16 MB/s       12322 B/op          4 allocs/op
    32  BenchmarkEncoder_Binding_JsonIter-16                   20056 ns/op         649.93 MB/s        9488 B/op          2 allocs/op
    33  BenchmarkEncoder_Binding_GoJson-16                      8311 ns/op        1568.32 MB/s        9481 B/op          1 allocs/op
    34  BenchmarkEncoder_Binding_StdLib-16                     16448 ns/op         792.52 MB/s        9479 B/op          1 allocs/op
    35  BenchmarkEncoder_Parallel_Generic_Sonic-16              6681 ns/op        1950.93 MB/s       12738 B/op          4 allocs/op
    36  BenchmarkEncoder_Parallel_Generic_Sonic_Fast-16         4179 ns/op        3118.99 MB/s       10757 B/op          4 allocs/op
    37  BenchmarkEncoder_Parallel_Generic_JsonIter-16           9861 ns/op        1321.84 MB/s       14362 B/op        115 allocs/op
    38  BenchmarkEncoder_Parallel_Generic_GoJson-16            18850 ns/op         691.52 MB/s       23278 B/op         16 allocs/op
    39  BenchmarkEncoder_Parallel_Generic_StdLib-16            45902 ns/op         283.97 MB/s       49174 B/op        789 allocs/op
    40  BenchmarkEncoder_Parallel_Binding_Sonic-16              1480 ns/op        8810.09 MB/s       13049 B/op          4 allocs/op
    41  BenchmarkEncoder_Parallel_Binding_Sonic_Fast-16         1209 ns/op        10785.23 MB/s      11546 B/op          4 allocs/op
    42  BenchmarkEncoder_Parallel_Binding_JsonIter-16           6170 ns/op        2112.58 MB/s        9504 B/op          2 allocs/op
    43  BenchmarkEncoder_Parallel_Binding_GoJson-16             3321 ns/op        3925.52 MB/s        9496 B/op          1 allocs/op
    44  BenchmarkEncoder_Parallel_Binding_StdLib-16             3739 ns/op        3486.49 MB/s        9480 B/op          1 allocs/op
    45  
    46  BenchmarkDecoder_Generic_Sonic-16                      66812 ns/op         195.10 MB/s       57602 B/op        723 allocs/op
    47  BenchmarkDecoder_Generic_Sonic_Fast-16                 54523 ns/op         239.07 MB/s       49786 B/op        313 allocs/op
    48  BenchmarkDecoder_Generic_StdLib-16                    124260 ns/op         104.90 MB/s       50869 B/op        772 allocs/op
    49  BenchmarkDecoder_Generic_JsonIter-16                   91274 ns/op         142.81 MB/s       55782 B/op       1068 allocs/op
    50  BenchmarkDecoder_Generic_GoJson-16                     88569 ns/op         147.17 MB/s       66367 B/op        973 allocs/op
    51  BenchmarkDecoder_Binding_Sonic-16                      32557 ns/op         400.38 MB/s       28302 B/op        137 allocs/op
    52  BenchmarkDecoder_Binding_Sonic_Fast-16                 28649 ns/op         455.00 MB/s       24999 B/op         34 allocs/op
    53  BenchmarkDecoder_Binding_StdLib-16                    111437 ns/op         116.97 MB/s       10576 B/op        208 allocs/op
    54  BenchmarkDecoder_Binding_JsonIter-16                   35090 ns/op         371.48 MB/s       14673 B/op        385 allocs/op
    55  BenchmarkDecoder_Binding_GoJson-16                     28738 ns/op         453.59 MB/s       22039 B/op         49 allocs/op
    56  BenchmarkDecoder_Parallel_Generic_Sonic-16             12321 ns/op        1057.91 MB/s       57233 B/op        723 allocs/op
    57  BenchmarkDecoder_Parallel_Generic_Sonic_Fast-16        10644 ns/op        1224.64 MB/s       49362 B/op        313 allocs/op
    58  BenchmarkDecoder_Parallel_Generic_StdLib-16            57587 ns/op         226.35 MB/s       50874 B/op        772 allocs/op
    59  BenchmarkDecoder_Parallel_Generic_JsonIter-16          38666 ns/op         337.12 MB/s       55789 B/op       1068 allocs/op
    60  BenchmarkDecoder_Parallel_Generic_GoJson-16            30259 ns/op         430.79 MB/s       66370 B/op        974 allocs/op
    61  BenchmarkDecoder_Parallel_Binding_Sonic-16              5965 ns/op        2185.28 MB/s       27747 B/op        137 allocs/op
    62  BenchmarkDecoder_Parallel_Binding_Sonic_Fast-16         5170 ns/op        2521.31 MB/s       24715 B/op         34 allocs/op
    63  BenchmarkDecoder_Parallel_Binding_StdLib-16            27582 ns/op         472.58 MB/s       10576 B/op        208 allocs/op
    64  BenchmarkDecoder_Parallel_Binding_JsonIter-16          13571 ns/op         960.51 MB/s       14685 B/op        385 allocs/op
    65  BenchmarkDecoder_Parallel_Binding_GoJson-16            10031 ns/op        1299.51 MB/s       22111 B/op         49 allocs/op
    66  
    67  BenchmarkGetOne_Sonic-16                                3276 ns/op        3975.78 MB/s          24 B/op          1 allocs/op
    68  BenchmarkGetOne_Gjson-16                                9431 ns/op        1380.81 MB/s           0 B/op          0 allocs/op
    69  BenchmarkGetOne_Jsoniter-16                            51178 ns/op         254.46 MB/s       27936 B/op        647 allocs/op
    70  BenchmarkGetOne_Parallel_Sonic-16                      216.7 ns/op       60098.95 MB/s          24 B/op          1 allocs/op
    71  BenchmarkGetOne_Parallel_Gjson-16                       1076 ns/op        12098.62 MB/s          0 B/op          0 allocs/op
    72  BenchmarkGetOne_Parallel_Jsoniter-16                   17741 ns/op         734.06 MB/s       27945 B/op        647 allocs/op
    73  BenchmarkSetOne_Sonic-16                               9571 ns/op         1360.61 MB/s        1584 B/op         17 allocs/op
    74  BenchmarkSetOne_Sjson-16                               36456 ns/op         357.22 MB/s       52180 B/op          9 allocs/op
    75  BenchmarkSetOne_Jsoniter-16                            79475 ns/op         163.86 MB/s       45862 B/op        964 allocs/op
    76  BenchmarkSetOne_Parallel_Sonic-16                      850.9 ns/op       15305.31 MB/s        1584 B/op         17 allocs/op
    77  BenchmarkSetOne_Parallel_Sjson-16                      18194 ns/op         715.77 MB/s       52247 B/op          9 allocs/op
    78  BenchmarkSetOne_Parallel_Jsoniter-16                   33560 ns/op         388.05 MB/s       45892 B/op        964 allocs/op
    79  BenchmarkLoadNode/LoadAll()-16                         11384 ns/op        1143.93 MB/s        6307 B/op         25 allocs/op
    80  BenchmarkLoadNode_Parallel/LoadAll()-16                 5493 ns/op        2370.68 MB/s        7145 B/op         25 allocs/op
    81  BenchmarkLoadNode/Interface()-16                       17722 ns/op         734.85 MB/s       13323 B/op         88 allocs/op
    82  BenchmarkLoadNode_Parallel/Interface()-16              10330 ns/op        1260.70 MB/s       15178 B/op         88 allocs/op
    83  ```
    84  - [Small](https://github.com/goshafaq/sonic/blob/main/testdata/small.go) (400B, 11 keys, 3 layers)
    85  ![small benchmarks](./docs/imgs/bench-small.png)
    86  - [Large](https://github.com/goshafaq/sonic/blob/main/testdata/twitter.json) (635KB, 10000+ key, 6 layers)
    87  ![large benchmarks](./docs/imgs/bench-large.png)
    88  
    89  See [bench.sh](https://github.com/goshafaq/sonic/blob/main/scripts/bench.sh) for benchmark codes.
    90  
    91  ## How it works
    92  See [INTRODUCTION.md](./docs/INTRODUCTION.md).
    93  
    94  ## Usage
    95  
    96  ### Marshal/Unmarshal
    97  
    98  Default behaviors are mostly consistent with `encoding/json`, except HTML escaping form (see [Escape HTML](https://github.com/goshafaq/sonic/blob/main/README.md#escape-html)) and `SortKeys` feature (optional support see [Sort Keys](https://github.com/goshafaq/sonic/blob/main/README.md#sort-keys)) that is **NOT** in conformity to [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259).
    99   ```go
   100  import "github.com/goshafaq/sonic"
   101  
   102  var data YourSchema
   103  // Marshal
   104  output, err := sonic.Marshal(&data)
   105  // Unmarshal
   106  err := sonic.Unmarshal(output, &data)
   107   ```
   108  
   109  ### Streaming IO
   110  Sonic supports decoding json from `io.Reader` or encoding objects into `io.Writer`, aims at handling multiple values as well as reducing memory consumption.
   111  - encoder
   112  ```go
   113  var o1 = map[string]interface{}{
   114      "a": "b",
   115  }
   116  var o2 = 1
   117  var w = bytes.NewBuffer(nil)
   118  var enc = sonic.ConfigDefault.NewEncoder(w)
   119  enc.Encode(o1)
   120  enc.Encode(o2)
   121  fmt.Println(w.String())
   122  // Output:
   123  // {"a":"b"}
   124  // 1
   125  ```
   126  - decoder
   127  ```go
   128  var o =  map[string]interface{}{}
   129  var r = strings.NewReader(`{"a":"b"}{"1":"2"}`)
   130  var dec = sonic.ConfigDefault.NewDecoder(r)
   131  dec.Decode(&o)
   132  dec.Decode(&o)
   133  fmt.Printf("%+v", o)
   134  // Output:
   135  // map[1:2 a:b]
   136  ```
   137  
   138  ### Use Number/Use Int64
   139   ```go
   140  import "github.com/goshafaq/sonic/decoder"
   141  
   142  var input = `1`
   143  var data interface{}
   144  
   145  // default float64
   146  dc := decoder.NewDecoder(input)
   147  dc.Decode(&data) // data == float64(1)
   148  // use json.Number
   149  dc = decoder.NewDecoder(input)
   150  dc.UseNumber()
   151  dc.Decode(&data) // data == json.Number("1")
   152  // use int64
   153  dc = decoder.NewDecoder(input)
   154  dc.UseInt64()
   155  dc.Decode(&data) // data == int64(1)
   156  
   157  root, err := sonic.GetFromString(input)
   158  // Get json.Number
   159  jn := root.Number()
   160  jm := root.InterfaceUseNumber().(json.Number) // jn == jm
   161  // Get float64
   162  fn := root.Float64()
   163  fm := root.Interface().(float64) // jn == jm
   164   ```
   165  
   166  ### Sort Keys
   167  On account of the performance loss from sorting (roughly 10%), sonic doesn't enable this feature by default. If your component depends on it to work (like [zstd](https://github.com/facebook/zstd)), Use it like this:
   168  ```go
   169  import "github.com/goshafaq/sonic"
   170  import "github.com/goshafaq/sonic/encoder"
   171  
   172  // Binding map only
   173  m := map[string]interface{}{}
   174  v, err := encoder.Encode(m, encoder.SortMapKeys)
   175  
   176  // Or ast.Node.SortKeys() before marshal
   177  var root := sonic.Get(JSON)
   178  err := root.SortKeys()
   179  ```
   180  ### Escape HTML
   181  On account of the performance loss (roughly 15%), sonic doesn't enable this feature by default. You can use `encoder.EscapeHTML` option to open this feature (align with `encoding/json.HTMLEscape`).
   182  ```go
   183  import "github.com/goshafaq/sonic"
   184  
   185  v := map[string]string{"&&":"<>"}
   186  ret, err := Encode(v, EscapeHTML) // ret == `{"\u0026\u0026":{"X":"\u003c\u003e"}}`
   187  ```
   188  ### Compact Format
   189  Sonic encodes primitive objects (struct/map...) as compact-format JSON by default, except marshaling `json.RawMessage` or `json.Marshaler`: sonic ensures validating their output JSON but **DONOT** compacting them for performance concerns. We provide the option `encoder.CompactMarshaler` to add compacting process.
   190  
   191  ### Print Error
   192  If there invalid syntax in input JSON, sonic will return `decoder.SyntaxError`, which supports pretty-printing of error position
   193  ```go
   194  import "github.com/goshafaq/sonic"
   195  import "github.com/goshafaq/sonic/decoder"
   196  
   197  var data interface{}
   198  err := sonic.UnmarshalString("[[[}]]", &data)
   199  if err != nil {
   200      /* One line by default */
   201      println(e.Error()) // "Syntax error at index 3: invalid char\n\n\t[[[}]]\n\t...^..\n"
   202      /* Pretty print */
   203      if e, ok := err.(decoder.SyntaxError); ok {
   204          /*Syntax error at index 3: invalid char
   205  
   206              [[[}]]
   207              ...^..
   208          */
   209          print(e.Description())
   210      } else if me, ok := err.(*decoder.MismatchTypeError); ok {
   211          // decoder.MismatchTypeError is new to Sonic v1.6.0
   212          print(me.Description())
   213      }
   214  }
   215  ```
   216  
   217  #### Mismatched Types [Sonic v1.6.0]
   218  If there a **mismatch-typed** value for a given key, sonic will report `decoder.MismatchTypeError` (if there are many, report the last one), but still skip wrong the value and keep decoding next JSON.
   219  ```go
   220  import "github.com/goshafaq/sonic"
   221  import "github.com/goshafaq/sonic/decoder"
   222  
   223  var data = struct{
   224      A int
   225      B int
   226  }{}
   227  err := UnmarshalString(`{"A":"1","B":1}`, &data)
   228  println(err.Error())    // Mismatch type int with value string "at index 5: mismatched type with value\n\n\t{\"A\":\"1\",\"B\":1}\n\t.....^.........\n"
   229  fmt.Printf("%+v", data) // {A:0 B:1}
   230  ```
   231  ### Ast.Node
   232  Sonic/ast.Node is a completely self-contained AST for JSON. It implements serialization and deserialization both and provides robust APIs for obtaining and modification of generic data.
   233  #### Get/Index
   234  Search partial JSON by given paths, which must be non-negative integer or string, or nil
   235  ```go
   236  import "github.com/goshafaq/sonic"
   237  
   238  input := []byte(`{"key1":[{},{"key2":{"key3":[1,2,3]}}]}`)
   239  
   240  // no path, returns entire json
   241  root, err := sonic.Get(input)
   242  raw := root.Raw() // == string(input)
   243  
   244  // multiple paths
   245  root, err := sonic.Get(input, "key1", 1, "key2")
   246  sub := root.Get("key3").Index(2).Int64() // == 3
   247  ```
   248  **Tip**: since `Index()` uses offset to locate data, which is much faster than scanning like `Get()`, we suggest you use it as much as possible. And sonic also provides another API `IndexOrGet()` to underlying use offset as well as ensure the key is matched.
   249  
   250  #### Set/Unset
   251  Modify the json content by Set()/Unset()
   252  ```go
   253  import "github.com/goshafaq/sonic"
   254  
   255  // Set
   256  exist, err := root.Set("key4", NewBool(true)) // exist == false
   257  alias1 := root.Get("key4")
   258  println(alias1.Valid()) // true
   259  alias2 := root.Index(1)
   260  println(alias1 == alias2) // true
   261  
   262  // Unset
   263  exist, err := root.UnsetByIndex(1) // exist == true
   264  println(root.Get("key4").Check()) // "value not exist"
   265  ```
   266  
   267  #### Serialize
   268  To encode `ast.Node` as json, use `MarshalJson()` or `json.Marshal()` (MUST pass the node's pointer)
   269  ```go
   270  import (
   271      "encoding/json"
   272      "github.com/goshafaq/sonic"
   273  )
   274  
   275  buf, err := root.MarshalJson()
   276  println(string(buf))                // {"key1":[{},{"key2":{"key3":[1,2,3]}}]}
   277  exp, err := json.Marshal(&root)     // WARN: use pointer
   278  println(string(buf) == string(exp)) // true
   279  ```
   280  
   281  #### APIs
   282  - validation: `Check()`, `Error()`, `Valid()`, `Exist()`
   283  - searching: `Index()`, `Get()`, `IndexPair()`, `IndexOrGet()`, `GetByPath()`
   284  - go-type casting: `Int64()`, `Float64()`, `String()`, `Number()`, `Bool()`, `Map[UseNumber|UseNode]()`, `Array[UseNumber|UseNode]()`, `Interface[UseNumber|UseNode]()`
   285  - go-type packing: `NewRaw()`, `NewNumber()`, `NewNull()`, `NewBool()`, `NewString()`, `NewObject()`, `NewArray()`
   286  - iteration: `Values()`, `Properties()`, `ForEach()`, `SortKeys()`
   287  - modification: `Set()`, `SetByIndex()`, `Add()`
   288  
   289  ### Ast.Visitor
   290  Sonic provides an advanced API for fully parsing JSON into non-standard types (neither `struct` not `map[string]interface{}`) without using any intermediate representation (`ast.Node` or `interface{}`). For example, you might have the following types which are like `interface{}` but actually not `interface{}`:
   291  ```go
   292  type UserNode interface {}
   293  
   294  // the following types implement the UserNode interface.
   295  type (
   296      UserNull    struct{}
   297      UserBool    struct{ Value bool }
   298      UserInt64   struct{ Value int64 }
   299      UserFloat64 struct{ Value float64 }
   300      UserString  struct{ Value string }
   301      UserObject  struct{ Value map[string]UserNode }
   302      UserArray   struct{ Value []UserNode }
   303  )
   304  ```
   305  Sonic provides the following API to return **the preorder traversal of a JSON AST**. The `ast.Visitor` is a SAX style interface which is used in some C++ JSON library. You should implement `ast.Visitor` by yourself and pass it to `ast.Preorder()` method. In your visitor you can make your custom types to represent JSON values. There may be an O(n) space container (such as stack) in your visitor to record the object / array hierarchy.
   306  ```go
   307  func Preorder(str string, visitor Visitor, opts *VisitorOptions) error
   308  
   309  type Visitor interface {
   310      OnNull() error
   311      OnBool(v bool) error
   312      OnString(v string) error
   313      OnInt64(v int64, n json.Number) error
   314      OnFloat64(v float64, n json.Number) error
   315      OnObjectBegin(capacity int) error
   316      OnObjectKey(key string) error
   317      OnObjectEnd() error
   318      OnArrayBegin(capacity int) error
   319      OnArrayEnd() error
   320  }
   321  ```
   322  
   323  See [ast/visitor.go](https://github.com/goshafaq/sonic/blob/main/ast/visitor.go) for detailed usage. We also implement a demo visitor for `UserNode` in [ast/visitor_test.go](https://github.com/goshafaq/sonic/blob/main/ast/visitor_test.go).
   324  
   325  ## Compatibility
   326  Sonic **DOES NOT** ensure to support all environments, due to the difficulty of developing high-performance codes. For developers who use sonic to build their applications in different environments, we have the following suggestions:
   327  
   328  - Developing on **Mac M1**: Make sure you have Rosetta 2 installed on your machine, and set `GOARCH=amd64` when building your application. Rosetta 2 can automatically translate x86 binaries to arm64 binaries and run x86 applications on Mac M1.
   329  - Developing on **Linux arm64**: You can install qemu and use the `qemu-x86_64 -cpu max` command to convert x86 binaries to amr64 binaries for applications built with sonic. The qemu can achieve a similar transfer effect to Rosetta 2 on Mac M1.
   330  
   331  For developers who want to use sonic on Linux arm64 without qemu, or those who want to handle JSON strictly consistent with `encoding/json`, we provide some compatible APIs as `sonic.API`
   332  - `ConfigDefault`: the sonic's default config (`EscapeHTML=false`,`SortKeys=false`...) to run on sonic-supporting environment. It will fall back to `encoding/json` with the corresponding config, and some options like `SortKeys=false` will be invalid.
   333  - `ConfigStd`: the std-compatible config (`EscapeHTML=true`,`SortKeys=true`...) to run on sonic-supporting environment. It will fall back to `encoding/json`.
   334  - `ConfigFastest`: the fastest config (`NoQuoteTextMarshaler=true`) to run on sonic-supporting environment. It will fall back to `encoding/json` with the corresponding config, and some options will be invalid.
   335  
   336  ## Tips
   337  
   338  ### Pretouch
   339  Since Sonic uses [golang-asm](https://github.com/twitchyliquid64/golang-asm) as a JIT assembler, which is NOT very suitable for runtime compiling, first-hit running of a huge schema may cause request-timeout or even process-OOM. For better stability, we advise **using `Pretouch()` for huge-schema or compact-memory applications** before `Marshal()/Unmarshal()`.
   340  ```go
   341  import (
   342      "reflect"
   343      "github.com/goshafaq/sonic"
   344      "github.com/goshafaq/sonic/option"
   345  )
   346  
   347  func init() {
   348      var v HugeStruct
   349  
   350      // For most large types (nesting depth <= option.DefaultMaxInlineDepth)
   351      err := sonic.Pretouch(reflect.TypeOf(v))
   352  
   353      // with more CompileOption...
   354      err := sonic.Pretouch(reflect.TypeOf(v),
   355          // If the type is too deep nesting (nesting depth > option.DefaultMaxInlineDepth),
   356          // you can set compile recursive loops in Pretouch for better stability in JIT.
   357          option.WithCompileRecursiveDepth(loop),
   358          // For a large nested struct, try to set a smaller depth to reduce compiling time.
   359          option.WithCompileMaxInlineDepth(depth),
   360      )
   361  }
   362  ```
   363  
   364  ### Copy string
   365  When decoding **string values without any escaped characters**, sonic references them from the origin JSON buffer instead of mallocing a new buffer to copy. This helps a lot for CPU performance but may leave the whole JSON buffer in memory as long as the decoded objects are being used. In practice, we found the extra memory introduced by referring JSON buffer is usually 20% ~ 80% of decoded objects. Once an application holds these objects for a long time (for example, cache the decoded objects for reusing), its in-use memory on the server may go up. We provide the option `decoder.CopyString()` for users to choose not to reference the JSON buffer, which may cause a decline in CPU performance to some degree.
   366  
   367  ### Pass string or []byte?
   368  For alignment to `encoding/json`, we provide API to pass `[]byte` as an argument, but the string-to-bytes copy is conducted at the same time considering safety, which may lose performance when the origin JSON is huge. Therefore, you can use `UnmarshalString()` and `GetFromString()` to pass a string, as long as your origin data is a string or **nocopy-cast** is safe for your []byte. We also provide API `MarshalString()` for convenient **nocopy-cast** of encoded JSON []byte, which is safe since sonic's output bytes is always duplicated and unique.
   369  
   370  ### Accelerate `encoding.TextMarshaler`
   371  To ensure data security, sonic.Encoder quotes and escapes string values from `encoding.TextMarshaler` interfaces by default, which may degrade performance much if most of your data is in form of them. We provide `encoder.NoQuoteTextMarshaler` to skip these operations, which means you **MUST** ensure their output string escaped and quoted following [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259).
   372  
   373  
   374  ### Better performance for generic data
   375  In **fully-parsed** scenario, `Unmarshal()` performs better than `Get()`+`Node.Interface()`. But if you only have a part of the schema for specific json, you can combine `Get()` and `Unmarshal()` together:
   376  ```go
   377  import "github.com/goshafaq/sonic"
   378  
   379  node, err := sonic.GetFromString(_TwitterJson, "statuses", 3, "user")
   380  var user User // your partial schema...
   381  err = sonic.UnmarshalString(node.Raw(), &user)
   382  ```
   383  Even if you don't have any schema, use `ast.Node` as the container of generic values instead of `map` or `interface`:
   384  ```go
   385  import "github.com/goshafaq/sonic"
   386  
   387  root, err := sonic.GetFromString(_TwitterJson)
   388  user := root.GetByPath("statuses", 3, "user")  // === root.Get("status").Index(3).Get("user")
   389  err = user.Check()
   390  
   391  // err = user.LoadAll() // only call this when you want to use 'user' concurrently...
   392  go someFunc(user)
   393  ```
   394  Why? Because `ast.Node` stores its children using `array`:
   395  - `Array`'s performance is **much better** than `Map` when Inserting (Deserialize) and Scanning (Serialize) data;
   396  - **Hashing** (`map[x]`) is not as efficient as **Indexing** (`array[x]`), which `ast.Node` can conduct on **both array and object**;
   397  - Using `Interface()`/`Map()` means Sonic must parse all the underlying values, while `ast.Node` can parse them **on demand**.
   398  
   399  **CAUTION:** `ast.Node` **DOESN'T** ensure concurrent security directly, due to its **lazy-load** design. However, you can call `Node.Load()`/`Node.LoadAll()` to achieve that, which may bring performance reduction while it still works faster than converting to `map` or `interface{}`
   400  
   401  ### Ast.Node or Ast.Visitor?
   402  For generic data, `ast.Node` should be enough for your needs in most cases.
   403  
   404  However, `ast.Node` is designed for partially processing JSON string. It has some special designs such as lazy-load which might not be suitable for directly parsing the whole JSON string like `Unmarshal()`. Although `ast.Node` is better then `map` or `interface{}`, it's also a kind of intermediate representation after all if your final types are customized and you have to convert the above types to your custom types after parsing.
   405  
   406  For better performance, in previous case the `ast.Visitor` will be the better choice. It performs JSON decoding like `Unmarshal()` and you can directly use your final types to represents a JSON AST without any intermediate representations.
   407  
   408  But `ast.Visitor` is not a very handy API. You might need to write a lot of code to implement your visitor and carefully maintain the tree hierarchy during decoding. Please read the comments in [ast/visitor.go](https://github.com/goshafaq/sonic/blob/main/ast/visitor.go) carefully if you decide to use this API.
   409  
   410  ## Community
   411  Sonic is a subproject of [CloudWeGo](https://www.cloudwego.io/). We are committed to building a cloud native ecosystem.