一个极其快速的 JSON 序列化和反序列化库
English | 中文
A blazingly fast JSON serializing & deserializing library, accelerated by JIT (just-in-time compiling) and SIMD (single-instruction-multiple-data).
-ldflags="-checklinkname=0".see go.dev
For all sizes of json and all scenarios of usage, Sonic performs best.
…
See bench.sh for benchmark codes.
See INTRODUCTION.md.
Default behaviors are mostly consistent with encoding/json, except HTML escaping form (see Escape HTML) and SortKeys feature (optional support see Sort Keys) that is NOT in conformity to RFC8259.
import "github.com/bytedance/sonic"
var data YourSchema
// Marshal
output, err := sonic.Marshal(&data)
// Unmarshal
err := sonic.Unmarshal(output, &data)
Sonic supports decoding json from io.Reader or encoding objects into io.Writer, aims at handling multiple values as well as reducing memory consumption.
var o1 = map[string]interface{}{
"a": "b",
}
var o2 = 1
var w = bytes.NewBuffer(nil)
var enc = sonic.ConfigDefault.NewEncoder(w)
enc.Encode(o1)
enc.Encode(o2)
fmt.Println(w.String())
// Output:
// {"a":"b"}
// 1
var o = map[string]interface{}{}
var r = strings.NewReader(`{"a":"b"}{"1":"2"}`)
var dec = sonic.ConfigDefault.NewDecoder(r)
dec.Decode(&o)
dec.Decode(&o)
fmt.Printf("%+v", o)
// Output:
// map[1:2 a:b]
…
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), Use it like this:
import "github.com/bytedance/sonic"
import "github.com/bytedance/sonic/encoder"
// Binding map only
m := map[string]interface{}{}
v, err := encoder.Encode(m, encoder.SortMapKeys)
// Or ast.Node.SortKeys() before marshal
var root := sonic.Get(JSON)
err := root.SortKeys()
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).
import "github.com/bytedance/sonic"
v := map[string]string{"&&":"<>"}
ret, err := Encode(v, EscapeHTML) // ret == `{"\u0026\u0026":{"X":"\u003c\u003e"}}`
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 DO NOT compacting them for performance concerns. We provide the option encoder.CompactMarshaler to add compacting process.
If there invalid syntax in input JSON, sonic will return decoder.SyntaxError, which supports pretty-printing of error position
…
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.
import "github.com/bytedance/sonic"
import "github.com/bytedance/sonic/decoder"
var data = struct{
A int
B int
}{}
err := UnmarshalString(`{"A":"1","B":1}`, &data)
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"
fmt.Printf("%+v", data) // {A:0 B:1}
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.
Search partial JSON by given paths, which must be non-negative integer or string, or nil
import "github.com/bytedance/sonic"
input := []byte(`{"key1":[{},{"key2":{"key3":[1,2,3]}}]}`)
// no path, returns entire json
root, err := sonic.Get(input)
raw := root.Raw() // == string(input)
// multiple paths
root, err := sonic.Get(input, "key1", 1, "key2")
sub := root.Get("key3").Index(2).Int64() // == 3
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.
Searcher provides some options for user to meet different needs:
opts := ast.SearchOption{ CopyReturn: true ... }
val, err := sonic.GetWithOptions(JSON, opts, "key")
ast.Node use Lazy-Load design, it doesn't support Concurrently-Read by default. If you want to read it concurrently, please specify it.Modify the json content by Set()/Unset()
import "github.com/bytedance/sonic"
// Set
exist, err := root.Set("key4", NewBool(true)) // exist == false
alias1 := root.Get("key4")
println(alias1.Valid()) // true
alias2 := root.Index(1)
println(alias1 == alias2) // true
// Unset
exist, err := root.UnsetByIndex(1) // exist == true
println(root.Get("key4").Check()) // "value not exist"
To encode ast.Node as json, use MarshalJson() or json.Marshal() (MUST pass the node's pointer)
import (
"encoding/json"
"github.com/bytedance/sonic"
)
buf, err := root.MarshalJson()
println(string(buf)) // {"key1":[{},{"key2":{"key3":[1,2,3]}}]}
exp, err := json.Marshal(&root) // WARN: use pointer
println(string(buf) == string(exp)) // true
Check(), Error(), Valid(), Exist()Index(), Get(), IndexPair(), IndexOrGet(), GetByPath()Int64(), Float64(), String(), Number(), Bool(), Map[UseNumber|UseNode](), Array[UseNumber|UseNode](), Interface[UseNumber|UseNode]()NewRaw(), NewNumber(), NewNull(), NewBool(), NewString(), NewObject(), NewArray()Values(), Properties(), ForEach(), SortKeys()Set(), SetByIndex(), Add()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{}:
type UserNode interface {}
// the following types implement the UserNode interface.
type (
UserNull struct{}
UserBool struct{ Value bool }
UserInt64 struct{ Value int64 }
UserFloat64 struct{ Value float64 }
UserString struct{ Value string }
UserObject struct{ Value map[string]UserNode }
UserArray struct{ Value []UserNode }
)
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.
func Preorder(str string, visitor Visitor, opts *VisitorOptions) error
type Visitor interface {
OnNull() error
OnBool(v bool) error
OnString(v string) error
OnInt64(v int64, n json.Number) error
OnFloat64(v float64, n json.Number) error
OnObjectBegin(capacity int) error
OnObjectKey(key string) error
OnObjectEnd() error
OnArrayBegin(capacity int) error
OnArrayEnd() error
}
See ast/visitor.go for detailed usage. We also implement a demo visitor for UserNode in ast/visitor_test.go.
For developers who want to use sonic to meet different scenarios, we provide some integrated configs as sonic.API
ConfigDefault: the sonic's default config (EscapeHTML=false,SortKeys=false...) to run sonic fast meanwhile ensure security.ConfigStd: the std-compatible config (EscapeHTML=true,SortKeys=true...)ConfigFastest: the fastest config (NoQuoteTextMarshaler=true) to run on sonic as fast as possible.
Sonic DOES NOT ensure to support all environments, due to the difficulty of developing high-performance codes. On non-sonic-supporting environment, the implementation will fall back to encoding/json. Thus below configs will all equal to ConfigStd.Since Sonic uses 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 PretouchMany() for huge-schema or lantency-sensitive applications before Marshal()/Unmarshal().
…
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.
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.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
暂无开放 Issues,或尚未同步最近议题。