Documentation
¶
Overview ¶
Package simdjson parses JSON by finding the whole document's structure in a few vector passes, then walking that instead of the bytes.
It is built on [simd.go](https://github.com/sebishogun/simd), so it needs no cgo and runs the same way on amd64, arm64, riscv64, s390x, ppc64le and loong64 — unlike the existing Go ports of simdjson, which are amd64 with hand-written assembly.
doc, err := simdjson.Parse(data)
name := doc.Get("user", "name").String()
age := doc.Get("user", "age").Int()
How it works ¶
Two stages, which is the design simdjson introduced.
Stage one finds every structural character — the braces, brackets, colons and commas — in one vector pass each, and works out which quotes really open and close strings rather than being escaped. A conventional parser reads a byte and branches on what it is, which is a dependent and unpredictable branch per byte; this makes eight branch-free passes over the document instead, and eight passes with no branches beat one pass with a branch per byte.
Stage two walks those positions. A document of a megabyte might have fifty thousand structural characters, so the second stage sees fifty thousand items rather than a million bytes.
What it is for ¶
Pulling a few values out of a document, which is most of what JSON is used for and the case encoding/json is worst at — it decodes everything to reach anything. Doc.Get navigates the index without decoding what it passes.
It is not a replacement for encoding/json. There is no struct unmarshalling, no tags, no interfaces, no streaming. If you want a Go value, use the standard library; if you want three fields out of a large payload, this is several times faster.
Example ¶
The case this package is for: a few values out of a document, without decoding the rest of it.
package main
import (
"fmt"
"github.com/sebishogun/simdjson"
)
func main() {
data := []byte(`{
"user": {"name": "ada", "age": 36, "tags": ["math", "engines"]},
"meta": {"page": 1}
}`)
doc, err := simdjson.Parse(data)
if err != nil {
fmt.Println("bad json:", err)
return
}
fmt.Println(doc.Get("user", "name").String())
fmt.Println(doc.Get("user", "age").Int())
fmt.Println(doc.Get("user", "tags").Index(1).String())
}
Output: ada 36 engines
Index ¶
- type Doc
- type Kind
- type Parser
- type Value
- func (v Value) Bool() bool
- func (v Value) Exists() bool
- func (v Value) Float() float64
- func (v Value) ForEach(fn func(Value) bool)
- func (v Value) ForEachKey(fn func(string, Value) bool)
- func (v Value) Index(n int) Value
- func (v Value) Int() int64
- func (v Value) IsNull() bool
- func (v Value) Key(name string) Value
- func (v Value) Kind() Kind
- func (v Value) Len() int
- func (v Value) Raw() []byte
- func (v Value) String() string
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Doc ¶
type Doc struct {
// contains filtered or unexported fields
}
Doc is a parsed document. It holds the input and its structural index; no values are decoded until they are asked for.
func Parse ¶
Parse indexes data and validates its structure.
The returned Doc keeps data — it is not copied, and every string a Value yields points into it unless the string contains an escape.
Example (StringsHideStructure) ¶
Structure inside a string is text, which is the whole difficulty of stage one and is handled before any of it is interpreted.
package main
import (
"fmt"
"github.com/sebishogun/simdjson"
)
func main() {
doc, err := simdjson.Parse([]byte(`{"a":"},{\"b\":2},[","c":1}`))
if err != nil {
fmt.Println("err:", err)
return
}
fmt.Printf("%q\n", doc.Get("a").String())
fmt.Println(doc.Get("c").Int())
}
Output: "},{\"b\":2},[" 1
func Scan ¶
Scan indexes data without validating it.
Parse walks the whole document and checks every value against JSON's grammar, which is what makes it safe for input you did not produce — and it is most of the cost. If the goal is three fields out of a payload your own service just serialised, validating the other nine thousand is work nobody asked for.
Scan skips it. The structural index is still built, so navigation works exactly as it does after Parse; what is gone is the recursive descent that proves the parts you never look at are well-formed.
What that costs ¶
Malformed input gives wrong answers rather than errors. A missing colon, a trailing comma, a number like 10., an invalid escape — all are accepted, and the values around them may come back wrong or absent instead of failing. The index itself is still consistent, so nothing reads out of bounds and nothing panics; the result is simply not to be trusted.
Two things are still checked, because the index cannot be built without them: every string is terminated, and quotes balance. A document that fails either is rejected here too.
Use Parse for anything from outside. Use Scan when you produced the bytes.
func (*Doc) Get ¶
Get walks a path of object keys and returns the value at the end.
A missing key, or a path that runs into a non-object, yields an Invalid Value rather than an error — chaining is the common case and an error at every step would be unusable. Check Value.Exists.
type Parser ¶
type Parser struct {
// contains filtered or unexported fields
}
Parser parses documents, reusing its index buffers between them.
A server handling many payloads should keep one per goroutine: Parse allocates a fresh index each time, and for a document of a few hundred kilobytes that index is several times the size of the document itself. A Parser reuses it, so the second and later documents allocate almost nothing.
A Parser is not safe for concurrent use.
Example ¶
A Parser reuses its index between documents, which is what a server handling a stream of payloads wants. The Doc it returns is only valid until the next Parse on the same Parser.
package main
import (
"fmt"
"github.com/sebishogun/simdjson"
)
func main() {
var p simdjson.Parser
for _, payload := range [][]byte{
[]byte(`{"id":1}`),
[]byte(`{"id":2}`),
} {
doc, err := p.Parse(payload)
if err != nil {
return
}
fmt.Println(doc.Get("id").Int())
}
}
Output: 1 2
type Value ¶
type Value struct {
// contains filtered or unexported fields
}
Value is one JSON value inside a document.
func (Value) Exists ¶
Exists reports whether the value was found.
Example ¶
A missing key yields a Value that does not exist rather than an error, so a path can be walked without checking every step.
package main
import (
"fmt"
"github.com/sebishogun/simdjson"
)
func main() {
doc, _ := simdjson.Parse([]byte(`{"a":{"b":1}}`))
fmt.Println(doc.Get("a", "b").Exists())
fmt.Println(doc.Get("a", "zzz").Exists())
fmt.Println(doc.Get("nope", "deeper").Exists())
}
Output: true false false
func (Value) ForEach ¶
ForEach calls fn for each element of an array until it returns false.
Example ¶
Iterating an array without building one.
package main
import (
"fmt"
"github.com/sebishogun/simdjson"
)
func main() {
doc, _ := simdjson.Parse([]byte(`{"scores":[10,20,30]}`))
total := int64(0)
doc.Get("scores").ForEach(func(v simdjson.Value) bool {
total += v.Int()
return true
})
fmt.Println(total)
}
Output: 60
func (Value) ForEachKey ¶
ForEachKey calls fn for each field of an object until it returns false.
Example ¶
Iterating an object's fields.
package main
import (
"fmt"
"github.com/sebishogun/simdjson"
)
func main() {
doc, _ := simdjson.Parse([]byte(`{"a":1,"b":2}`))
doc.Root().ForEachKey(func(k string, v simdjson.Value) bool {
fmt.Printf("%s=%d\n", k, v.Int())
return true
})
}
Output: a=1 b=2
func (Value) Key ¶
Key returns the value of a field in an object.
The scan walks the object's structural entries rather than its bytes, so passing over a large nested value costs one bracket match instead of a parse. A missing key gives an Invalid Value; see Value.Exists.