github.com/bytedance/sonic@v1.11.7-0.20240517092252-d2edb31b167b/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 9 - Go 1.16~1.22 10 - Linux / MacOS / Windows(need go1.17 above) 11 - Amd64 ARCH 12 13 ## Features 14 15 - Runtime object binding without code generation 16 - Complete APIs for JSON value manipulation 17 - Fast, fast, fast! 18 19 ## APIs 20 21 see [go.dev](https://pkg.go.dev/github.com/bytedance/sonic) 22 23 ## Benchmarks 24 25 For **all sizes** of json and **all scenarios** of usage, **Sonic performs best**. 26 27 - [Medium](https://github.com/bytedance/sonic/blob/main/decoder/testdata_test.go#L19) (13KB, 300+ key, 6 layers) 28 29 ```powershell 30 goversion: 1.17.1 31 goos: darwin 32 goarch: amd64 33 cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz 34 BenchmarkEncoder_Generic_Sonic-16 32393 ns/op 402.40 MB/s 11965 B/op 4 allocs/op 35 BenchmarkEncoder_Generic_Sonic_Fast-16 21668 ns/op 601.57 MB/s 10940 B/op 4 allocs/op 36 BenchmarkEncoder_Generic_JsonIter-16 42168 ns/op 309.12 MB/s 14345 B/op 115 allocs/op 37 BenchmarkEncoder_Generic_GoJson-16 65189 ns/op 199.96 MB/s 23261 B/op 16 allocs/op 38 BenchmarkEncoder_Generic_StdLib-16 106322 ns/op 122.60 MB/s 49136 B/op 789 allocs/op 39 BenchmarkEncoder_Binding_Sonic-16 6269 ns/op 2079.26 MB/s 14173 B/op 4 allocs/op 40 BenchmarkEncoder_Binding_Sonic_Fast-16 5281 ns/op 2468.16 MB/s 12322 B/op 4 allocs/op 41 BenchmarkEncoder_Binding_JsonIter-16 20056 ns/op 649.93 MB/s 9488 B/op 2 allocs/op 42 BenchmarkEncoder_Binding_GoJson-16 8311 ns/op 1568.32 MB/s 9481 B/op 1 allocs/op 43 BenchmarkEncoder_Binding_StdLib-16 16448 ns/op 792.52 MB/s 9479 B/op 1 allocs/op 44 BenchmarkEncoder_Parallel_Generic_Sonic-16 6681 ns/op 1950.93 MB/s 12738 B/op 4 allocs/op 45 BenchmarkEncoder_Parallel_Generic_Sonic_Fast-16 4179 ns/op 3118.99 MB/s 10757 B/op 4 allocs/op 46 BenchmarkEncoder_Parallel_Generic_JsonIter-16 9861 ns/op 1321.84 MB/s 14362 B/op 115 allocs/op 47 BenchmarkEncoder_Parallel_Generic_GoJson-16 18850 ns/op 691.52 MB/s 23278 B/op 16 allocs/op 48 BenchmarkEncoder_Parallel_Generic_StdLib-16 45902 ns/op 283.97 MB/s 49174 B/op 789 allocs/op 49 BenchmarkEncoder_Parallel_Binding_Sonic-16 1480 ns/op 8810.09 MB/s 13049 B/op 4 allocs/op 50 BenchmarkEncoder_Parallel_Binding_Sonic_Fast-16 1209 ns/op 10785.23 MB/s 11546 B/op 4 allocs/op 51 BenchmarkEncoder_Parallel_Binding_JsonIter-16 6170 ns/op 2112.58 MB/s 9504 B/op 2 allocs/op 52 BenchmarkEncoder_Parallel_Binding_GoJson-16 3321 ns/op 3925.52 MB/s 9496 B/op 1 allocs/op 53 BenchmarkEncoder_Parallel_Binding_StdLib-16 3739 ns/op 3486.49 MB/s 9480 B/op 1 allocs/op 54 55 BenchmarkDecoder_Generic_Sonic-16 66812 ns/op 195.10 MB/s 57602 B/op 723 allocs/op 56 BenchmarkDecoder_Generic_Sonic_Fast-16 54523 ns/op 239.07 MB/s 49786 B/op 313 allocs/op 57 BenchmarkDecoder_Generic_StdLib-16 124260 ns/op 104.90 MB/s 50869 B/op 772 allocs/op 58 BenchmarkDecoder_Generic_JsonIter-16 91274 ns/op 142.81 MB/s 55782 B/op 1068 allocs/op 59 BenchmarkDecoder_Generic_GoJson-16 88569 ns/op 147.17 MB/s 66367 B/op 973 allocs/op 60 BenchmarkDecoder_Binding_Sonic-16 32557 ns/op 400.38 MB/s 28302 B/op 137 allocs/op 61 BenchmarkDecoder_Binding_Sonic_Fast-16 28649 ns/op 455.00 MB/s 24999 B/op 34 allocs/op 62 BenchmarkDecoder_Binding_StdLib-16 111437 ns/op 116.97 MB/s 10576 B/op 208 allocs/op 63 BenchmarkDecoder_Binding_JsonIter-16 35090 ns/op 371.48 MB/s 14673 B/op 385 allocs/op 64 BenchmarkDecoder_Binding_GoJson-16 28738 ns/op 453.59 MB/s 22039 B/op 49 allocs/op 65 BenchmarkDecoder_Parallel_Generic_Sonic-16 12321 ns/op 1057.91 MB/s 57233 B/op 723 allocs/op 66 BenchmarkDecoder_Parallel_Generic_Sonic_Fast-16 10644 ns/op 1224.64 MB/s 49362 B/op 313 allocs/op 67 BenchmarkDecoder_Parallel_Generic_StdLib-16 57587 ns/op 226.35 MB/s 50874 B/op 772 allocs/op 68 BenchmarkDecoder_Parallel_Generic_JsonIter-16 38666 ns/op 337.12 MB/s 55789 B/op 1068 allocs/op 69 BenchmarkDecoder_Parallel_Generic_GoJson-16 30259 ns/op 430.79 MB/s 66370 B/op 974 allocs/op 70 BenchmarkDecoder_Parallel_Binding_Sonic-16 5965 ns/op 2185.28 MB/s 27747 B/op 137 allocs/op 71 BenchmarkDecoder_Parallel_Binding_Sonic_Fast-16 5170 ns/op 2521.31 MB/s 24715 B/op 34 allocs/op 72 BenchmarkDecoder_Parallel_Binding_StdLib-16 27582 ns/op 472.58 MB/s 10576 B/op 208 allocs/op 73 BenchmarkDecoder_Parallel_Binding_JsonIter-16 13571 ns/op 960.51 MB/s 14685 B/op 385 allocs/op 74 BenchmarkDecoder_Parallel_Binding_GoJson-16 10031 ns/op 1299.51 MB/s 22111 B/op 49 allocs/op 75 76 BenchmarkGetOne_Sonic-16 3276 ns/op 3975.78 MB/s 24 B/op 1 allocs/op 77 BenchmarkGetOne_Gjson-16 9431 ns/op 1380.81 MB/s 0 B/op 0 allocs/op 78 BenchmarkGetOne_Jsoniter-16 51178 ns/op 254.46 MB/s 27936 B/op 647 allocs/op 79 BenchmarkGetOne_Parallel_Sonic-16 216.7 ns/op 60098.95 MB/s 24 B/op 1 allocs/op 80 BenchmarkGetOne_Parallel_Gjson-16 1076 ns/op 12098.62 MB/s 0 B/op 0 allocs/op 81 BenchmarkGetOne_Parallel_Jsoniter-16 17741 ns/op 734.06 MB/s 27945 B/op 647 allocs/op 82 BenchmarkSetOne_Sonic-16 9571 ns/op 1360.61 MB/s 1584 B/op 17 allocs/op 83 BenchmarkSetOne_Sjson-16 36456 ns/op 357.22 MB/s 52180 B/op 9 allocs/op 84 BenchmarkSetOne_Jsoniter-16 79475 ns/op 163.86 MB/s 45862 B/op 964 allocs/op 85 BenchmarkSetOne_Parallel_Sonic-16 850.9 ns/op 15305.31 MB/s 1584 B/op 17 allocs/op 86 BenchmarkSetOne_Parallel_Sjson-16 18194 ns/op 715.77 MB/s 52247 B/op 9 allocs/op 87 BenchmarkSetOne_Parallel_Jsoniter-16 33560 ns/op 388.05 MB/s 45892 B/op 964 allocs/op 88 BenchmarkLoadNode/LoadAll()-16 11384 ns/op 1143.93 MB/s 6307 B/op 25 allocs/op 89 BenchmarkLoadNode_Parallel/LoadAll()-16 5493 ns/op 2370.68 MB/s 7145 B/op 25 allocs/op 90 BenchmarkLoadNode/Interface()-16 17722 ns/op 734.85 MB/s 13323 B/op 88 allocs/op 91 BenchmarkLoadNode_Parallel/Interface()-16 10330 ns/op 1260.70 MB/s 15178 B/op 88 allocs/op 92 ``` 93 94 - [Small](https://github.com/bytedance/sonic/blob/main/testdata/small.go) (400B, 11 keys, 3 layers) 95 ![small benchmarks](./docs/imgs/bench-small.png) 96 - [Large](https://github.com/bytedance/sonic/blob/main/testdata/twitter.json) (635KB, 10000+ key, 6 layers) 97 ![large benchmarks](./docs/imgs/bench-large.png) 98 99 See [bench.sh](https://github.com/bytedance/sonic/blob/main/scripts/bench.sh) for benchmark codes. 100 101 ## How it works 102 103 See [INTRODUCTION.md](./docs/INTRODUCTION.md). 104 105 ## Usage 106 107 ### Marshal/Unmarshal 108 109 Default behaviors are mostly consistent with `encoding/json`, except HTML escaping form (see [Escape HTML](https://github.com/bytedance/sonic/blob/main/README.md#escape-html)) and `SortKeys` feature (optional support see [Sort Keys](https://github.com/bytedance/sonic/blob/main/README.md#sort-keys)) that is **NOT** in conformity to [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259). 110 111 ```go 112 import "github.com/bytedance/sonic" 113 114 var data YourSchema 115 // Marshal 116 output, err := sonic.Marshal(&data) 117 // Unmarshal 118 err := sonic.Unmarshal(output, &data) 119 ``` 120 121 ### Streaming IO 122 123 Sonic supports decoding json from `io.Reader` or encoding objects into `io.Writer`, aims at handling multiple values as well as reducing memory consumption. 124 125 - encoder 126 127 ```go 128 var o1 = map[string]interface{}{ 129 "a": "b", 130 } 131 var o2 = 1 132 var w = bytes.NewBuffer(nil) 133 var enc = sonic.ConfigDefault.NewEncoder(w) 134 enc.Encode(o1) 135 enc.Encode(o2) 136 fmt.Println(w.String()) 137 // Output: 138 // {"a":"b"} 139 // 1 140 ``` 141 142 - decoder 143 144 ```go 145 var o = map[string]interface{}{} 146 var r = strings.NewReader(`{"a":"b"}{"1":"2"}`) 147 var dec = sonic.ConfigDefault.NewDecoder(r) 148 dec.Decode(&o) 149 dec.Decode(&o) 150 fmt.Printf("%+v", o) 151 // Output: 152 // map[1:2 a:b] 153 ``` 154 155 ### Use Number/Use Int64 156 157 ```go 158 import "github.com/bytedance/sonic/decoder" 159 160 var input = `1` 161 var data interface{} 162 163 // default float64 164 dc := decoder.NewDecoder(input) 165 dc.Decode(&data) // data == float64(1) 166 // use json.Number 167 dc = decoder.NewDecoder(input) 168 dc.UseNumber() 169 dc.Decode(&data) // data == json.Number("1") 170 // use int64 171 dc = decoder.NewDecoder(input) 172 dc.UseInt64() 173 dc.Decode(&data) // data == int64(1) 174 175 root, err := sonic.GetFromString(input) 176 // Get json.Number 177 jn := root.Number() 178 jm := root.InterfaceUseNumber().(json.Number) // jn == jm 179 // Get float64 180 fn := root.Float64() 181 fm := root.Interface().(float64) // jn == jm 182 ``` 183 184 ### Sort Keys 185 186 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: 187 188 ```go 189 import "github.com/bytedance/sonic" 190 import "github.com/bytedance/sonic/encoder" 191 192 // Binding map only 193 m := map[string]interface{}{} 194 v, err := encoder.Encode(m, encoder.SortMapKeys) 195 196 // Or ast.Node.SortKeys() before marshal 197 var root := sonic.Get(JSON) 198 err := root.SortKeys() 199 ``` 200 201 ### Escape HTML 202 203 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`). 204 205 ```go 206 import "github.com/bytedance/sonic" 207 208 v := map[string]string{"&&":"<>"} 209 ret, err := Encode(v, EscapeHTML) // ret == `{"\u0026\u0026":{"X":"\u003c\u003e"}}` 210 ``` 211 212 ### Compact Format 213 214 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. 215 216 ### Print Error 217 218 If there invalid syntax in input JSON, sonic will return `decoder.SyntaxError`, which supports pretty-printing of error position 219 220 ```go 221 import "github.com/bytedance/sonic" 222 import "github.com/bytedance/sonic/decoder" 223 224 var data interface{} 225 err := sonic.UnmarshalString("[[[}]]", &data) 226 if err != nil { 227 /* One line by default */ 228 println(e.Error()) // "Syntax error at index 3: invalid char\n\n\t[[[}]]\n\t...^..\n" 229 /* Pretty print */ 230 if e, ok := err.(decoder.SyntaxError); ok { 231 /*Syntax error at index 3: invalid char 232 233 [[[}]] 234 ...^.. 235 */ 236 print(e.Description()) 237 } else if me, ok := err.(*decoder.MismatchTypeError); ok { 238 // decoder.MismatchTypeError is new to Sonic v1.6.0 239 print(me.Description()) 240 } 241 } 242 ``` 243 244 #### Mismatched Types [Sonic v1.6.0] 245 246 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. 247 248 ```go 249 import "github.com/bytedance/sonic" 250 import "github.com/bytedance/sonic/decoder" 251 252 var data = struct{ 253 A int 254 B int 255 }{} 256 err := UnmarshalString(`{"A":"1","B":1}`, &data) 257 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" 258 fmt.Printf("%+v", data) // {A:0 B:1} 259 ``` 260 261 ### Ast.Node 262 263 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. 264 265 #### Get/Index 266 267 Search partial JSON by given paths, which must be non-negative integer or string, or nil 268 269 ```go 270 import "github.com/bytedance/sonic" 271 272 input := []byte(`{"key1":[{},{"key2":{"key3":[1,2,3]}}]}`) 273 274 // no path, returns entire json 275 root, err := sonic.Get(input) 276 raw := root.Raw() // == string(input) 277 278 // multiple paths 279 root, err := sonic.Get(input, "key1", 1, "key2") 280 sub := root.Get("key3").Index(2).Int64() // == 3 281 ``` 282 283 **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. 284 285 #### Set/Unset 286 287 Modify the json content by Set()/Unset() 288 289 ```go 290 import "github.com/bytedance/sonic" 291 292 // Set 293 exist, err := root.Set("key4", NewBool(true)) // exist == false 294 alias1 := root.Get("key4") 295 println(alias1.Valid()) // true 296 alias2 := root.Index(1) 297 println(alias1 == alias2) // true 298 299 // Unset 300 exist, err := root.UnsetByIndex(1) // exist == true 301 println(root.Get("key4").Check()) // "value not exist" 302 ``` 303 304 #### Serialize 305 306 To encode `ast.Node` as json, use `MarshalJson()` or `json.Marshal()` (MUST pass the node's pointer) 307 308 ```go 309 import ( 310 "encoding/json" 311 "github.com/bytedance/sonic" 312 ) 313 314 buf, err := root.MarshalJson() 315 println(string(buf)) // {"key1":[{},{"key2":{"key3":[1,2,3]}}]} 316 exp, err := json.Marshal(&root) // WARN: use pointer 317 println(string(buf) == string(exp)) // true 318 ``` 319 320 #### APIs 321 322 - validation: `Check()`, `Error()`, `Valid()`, `Exist()` 323 - searching: `Index()`, `Get()`, `IndexPair()`, `IndexOrGet()`, `GetByPath()` 324 - go-type casting: `Int64()`, `Float64()`, `String()`, `Number()`, `Bool()`, `Map[UseNumber|UseNode]()`, `Array[UseNumber|UseNode]()`, `Interface[UseNumber|UseNode]()` 325 - go-type packing: `NewRaw()`, `NewNumber()`, `NewNull()`, `NewBool()`, `NewString()`, `NewObject()`, `NewArray()` 326 - iteration: `Values()`, `Properties()`, `ForEach()`, `SortKeys()` 327 - modification: `Set()`, `SetByIndex()`, `Add()` 328 329 ### Ast.Visitor 330 331 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{}`: 332 333 ```go 334 type UserNode interface {} 335 336 // the following types implement the UserNode interface. 337 type ( 338 UserNull struct{} 339 UserBool struct{ Value bool } 340 UserInt64 struct{ Value int64 } 341 UserFloat64 struct{ Value float64 } 342 UserString struct{ Value string } 343 UserObject struct{ Value map[string]UserNode } 344 UserArray struct{ Value []UserNode } 345 ) 346 ``` 347 348 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. 349 350 ```go 351 func Preorder(str string, visitor Visitor, opts *VisitorOptions) error 352 353 type Visitor interface { 354 OnNull() error 355 OnBool(v bool) error 356 OnString(v string) error 357 OnInt64(v int64, n json.Number) error 358 OnFloat64(v float64, n json.Number) error 359 OnObjectBegin(capacity int) error 360 OnObjectKey(key string) error 361 OnObjectEnd() error 362 OnArrayBegin(capacity int) error 363 OnArrayEnd() error 364 } 365 ``` 366 367 See [ast/visitor.go](https://github.com/bytedance/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/bytedance/sonic/blob/main/ast/visitor_test.go). 368 369 ## Compatibility 370 371 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: 372 373 - 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. 374 - 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. 375 376 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` 377 378 - `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. 379 - `ConfigStd`: the std-compatible config (`EscapeHTML=true`,`SortKeys=true`...) to run on sonic-supporting environment. It will fall back to `encoding/json`. 380 - `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. 381 382 ## Tips 383 384 ### Pretouch 385 386 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()`. 387 388 ```go 389 import ( 390 "reflect" 391 "github.com/bytedance/sonic" 392 "github.com/bytedance/sonic/option" 393 ) 394 395 func init() { 396 var v HugeStruct 397 398 // For most large types (nesting depth <= option.DefaultMaxInlineDepth) 399 err := sonic.Pretouch(reflect.TypeOf(v)) 400 401 // with more CompileOption... 402 err := sonic.Pretouch(reflect.TypeOf(v), 403 // If the type is too deep nesting (nesting depth > option.DefaultMaxInlineDepth), 404 // you can set compile recursive loops in Pretouch for better stability in JIT. 405 option.WithCompileRecursiveDepth(loop), 406 // For a large nested struct, try to set a smaller depth to reduce compiling time. 407 option.WithCompileMaxInlineDepth(depth), 408 ) 409 } 410 ``` 411 412 ### Copy string 413 414 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. - `Config.CopyString`/`decoder.CopyString()`: We provide the option for `Decode()` / `Unmarshal()` users to choose not to reference the JSON buffer, which may cause a decline in CPU performance to some degree. 415 416 - `GetFromStringNoCopy()`: For memory safety, `sonic.Get()` / `sonic.GetFromString()` now copies return JSON. If users want to get json more quickly and not care about memory usage, you can use `GetFromStringNoCopy()` to return a JSON directly referenced from source. 417 418 ### Pass string or []byte? 419 420 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. 421 422 ### Accelerate `encoding.TextMarshaler` 423 424 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). 425 426 ### Better performance for generic data 427 428 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: 429 430 ```go 431 import "github.com/bytedance/sonic" 432 433 node, err := sonic.GetFromString(_TwitterJson, "statuses", 3, "user") 434 var user User // your partial schema... 435 err = sonic.UnmarshalString(node.Raw(), &user) 436 ``` 437 438 Even if you don't have any schema, use `ast.Node` as the container of generic values instead of `map` or `interface`: 439 440 ```go 441 import "github.com/bytedance/sonic" 442 443 root, err := sonic.GetFromString(_TwitterJson) 444 user := root.GetByPath("statuses", 3, "user") // === root.Get("status").Index(3).Get("user") 445 err = user.Check() 446 447 // err = user.LoadAll() // only call this when you want to use 'user' concurrently... 448 go someFunc(user) 449 ``` 450 451 Why? Because `ast.Node` stores its children using `array`: 452 453 - `Array`'s performance is **much better** than `Map` when Inserting (Deserialize) and Scanning (Serialize) data; 454 - **Hashing** (`map[x]`) is not as efficient as **Indexing** (`array[x]`), which `ast.Node` can conduct on **both array and object**; 455 - Using `Interface()`/`Map()` means Sonic must parse all the underlying values, while `ast.Node` can parse them **on demand**. 456 457 **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{}` 458 459 ### Ast.Node or Ast.Visitor? 460 461 For generic data, `ast.Node` should be enough for your needs in most cases. 462 463 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. 464 465 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. 466 467 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/bytedance/sonic/blob/main/ast/visitor.go) carefully if you decide to use this API. 468 469 ## Community 470 471 Sonic is a subproject of [CloudWeGo](https://www.cloudwego.io/). We are committed to building a cloud native ecosystem.