Documentation
¶
Overview ¶
Package jsonfast is a zero-allocation JSON builder and scanner for fixed schemas.
Usage ¶
b := jsonfast.Acquire()
b.BeginObject()
b.AddStringField("message", "hello")
b.AddIntField("severity", 3)
b.AddTimeRFC3339Field("timestamp", time.Now())
b.EndObject()
data := b.Bytes()
jsonfast.Release(b)
The zero value of Builder is ready to use.
Pre-computed keys ¶
var keyMessage = jsonfast.NewFieldKey("message")
b.AddStringFieldKey(keyMessage, "hello")
Field names passed to Add*Field are JSON-escaped on output, so any Go string is safe. NewFieldKey caches the prefix verbatim and therefore requires a safe-ASCII name (printable ASCII excluding '"' and '\\'); it panics on any other input, like regexp.MustCompile.
Numbers ¶
Float fields match encoding/json output: shortest round-trip form, exponent notation for magnitudes below 1e-6 or at least 1e21, and null for NaN and ±Inf. Integral floats in (-1e18, 1e18) are emitted in exact integer form.
Scanning ¶
IterateFields, FindField, IterateArray, IterateStringArray parse JSON structurally without building a DOM. IterateFields and IterateArray return true only for a single complete value with at most trailing whitespace; trailing commas and trailing content are rejected. FindField stops at the first match. The *String variants take a string input and alias its backing memory; slices/strings passed to callbacks must not outlive the call (clone via strings.Clone to retain).
Raw JSON value bytes returned by the scanners can be promoted to Go values via DecodeString, DecodeBool, DecodeInt64, DecodeUint64, and DecodeFloat64. Each returns ok=false on malformed input; DecodeString rejects raw control bytes and unescaped quotes, and the integer decoders also reject fractional, exponent, leading '+', and leading-zero forms.
Out-of-scope ¶
jsonfast intentionally omits a generic JSON-to-DOM decoder (map[string]any / []any), a generic any-encoder for Builder, and a whitespace-compacting helper. Fixed-schema callers should use the Iterate* / FindField scanners and the typed Add*Field methods directly. When dynamic data is genuinely required, encode it with encoding/json and splice the result via Builder.AppendRaw or Builder.AddRawJSONField; for whitespace compaction use encoding/json.Compact.
NDJSON ¶
BatchWriter appends JSON records separated by '\n' and implements io.Writer (one record per Write) and io.WriterTo.
Portability ¶
The SWAR fast paths use a single unaligned 8-byte load on architectures that support it (amd64, arm64, ppc64le, s390x) and a byte-wise fallback elsewhere. Build with -tags=purego to force the fallback on every architecture.
Index ¶
- func DecodeBool(raw []byte) (value, ok bool)
- func DecodeFloat64(raw []byte) (float64, bool)
- func DecodeInt64(raw []byte) (int64, bool)
- func DecodeString(raw []byte) (string, bool)
- func DecodeUint64(raw []byte) (uint64, bool)
- func EscapeString(s string) string
- func FindField(data []byte, key string) ([]byte, bool)
- func FindFieldString(s, key string) ([]byte, bool)
- func FlattenMap(m map[string]map[string]string, dst map[string]string) map[string]string
- func FlattenObject(b *Builder, data []byte) bool
- func IsStructuralJSON(s string) bool
- func IterateArray(data []byte, fn func(element []byte) bool) bool
- func IterateArrayString(s string, fn func(element []byte) bool) bool
- func IterateFields(data []byte, fn func(key, value []byte) bool) bool
- func IterateFieldsString(s string, fn func(key, value []byte) bool) bool
- func IterateStringArray(data []byte, fn func(val string) bool) bool
- func IterateStringArrayString(s string, fn func(val string) bool) bool
- func Release(b *Builder)
- func ReleaseBatchWriter(bw *BatchWriter)
- func SkipBracedAt(data []byte, i int, opener, closer byte) (int, bool)
- func SkipStringAt(data []byte, i int) (int, bool)
- func SkipValueAt(data []byte, i int) (int, bool)
- func SkipWS(data []byte, i int) int
- func WarmBatchWriterPool(n int)
- func WarmPool(n int)
- type BatchWriter
- func (w *BatchWriter) Append(record []byte)
- func (w *BatchWriter) AppendString(record string)
- func (w *BatchWriter) Bytes() []byte
- func (w *BatchWriter) Count() int
- func (w *BatchWriter) Grow(n int)
- func (w *BatchWriter) Len() int
- func (w *BatchWriter) Reset()
- func (w *BatchWriter) Write(p []byte) (int, error)
- func (w *BatchWriter) WriteTo(target io.Writer) (int64, error)
- type Builder
- func (b *Builder) AddBoolField(name string, v bool)
- func (b *Builder) AddBoolFieldKey(k FieldKey, v bool)
- func (b *Builder) AddFlattenedMapField(m map[string]map[string]string)
- func (b *Builder) AddFloat64Field(name string, v float64)
- func (b *Builder) AddFloat64FieldKey(k FieldKey, v float64)
- func (b *Builder) AddInt64Field(name string, v int64)
- func (b *Builder) AddInt64FieldKey(k FieldKey, v int64)
- func (b *Builder) AddIntField(name string, v int)
- func (b *Builder) AddIntFieldKey(k FieldKey, v int)
- func (b *Builder) AddNestedStringMapField(name string, m map[string]map[string]string)
- func (b *Builder) AddNullField(name string)
- func (b *Builder) AddNullFieldKey(k FieldKey)
- func (b *Builder) AddRawBytesField(name, value []byte)
- func (b *Builder) AddRawJSONField(name string, rawJSON []byte)
- func (b *Builder) AddRawJSONFieldKey(k FieldKey, rawJSON []byte)
- func (b *Builder) AddStringArrayField(name string, values []string)
- func (b *Builder) AddStringArrayFieldKey(k FieldKey, values []string)
- func (b *Builder) AddStringField(name, value string)
- func (b *Builder) AddStringFieldKey(k FieldKey, value string)
- func (b *Builder) AddStringMapObject(m map[string]string, rawJSONKey string)
- func (b *Builder) AddStringMapObjectField(name string, m map[string]string, rawJSONKey string)
- func (b *Builder) AddTimeRFC3339Field(name string, t time.Time)
- func (b *Builder) AddTimeRFC3339FieldKey(k FieldKey, t time.Time)
- func (b *Builder) AddTimeRFC3339OffsetField(name string, t time.Time)
- func (b *Builder) AddTimeRFC3339OffsetFieldKey(k FieldKey, t time.Time)
- func (b *Builder) AddUint64Field(name string, v uint64)
- func (b *Builder) AddUint64FieldKey(k FieldKey, v uint64)
- func (b *Builder) AppendEscapedString(s string)
- func (b *Builder) AppendRaw(p []byte)
- func (b *Builder) AppendRawString(s string)
- func (b *Builder) BeginObject()
- func (b *Builder) BeginObjectField(name string)
- func (b *Builder) BeginObjectFieldKey(k FieldKey)
- func (b *Builder) Bytes() []byte
- func (b *Builder) EndObject()
- func (b *Builder) EndObjectField()
- func (b *Builder) Grow(n int)
- func (b *Builder) Len() int
- func (b *Builder) Reset()
- func (b *Builder) Write(p []byte) (int, error)
- func (b *Builder) WriteTo(w io.Writer) (int64, error)
- type FieldKey
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DecodeBool ¶ added in v0.2.0
DecodeBool decodes "true" or "false".
func DecodeFloat64 ¶ added in v0.2.0
DecodeFloat64 decodes any RFC 8259 number into a float64. NaN and Inf are rejected.
func DecodeInt64 ¶ added in v0.2.0
DecodeInt64 decodes a JSON integer. Rejects fractional forms, exponents, leading '+' and leading zeros (except "0" and "-0").
func DecodeString ¶ added in v0.2.0
DecodeString decodes a JSON string (including surrounding quotes) into its Go form. The input must be exactly one grammar-valid JSON string: raw control bytes, unescaped quotes, and trailing content are rejected. The returned string is a fresh allocation.
func DecodeUint64 ¶ added in v0.2.0
DecodeUint64 decodes a non-negative JSON integer. Same rejection rules as DecodeInt64.
func EscapeString ¶
EscapeString returns s with JSON escaping. If s is already safe ASCII it is returned unchanged (zero allocation). Invalid UTF-8 sequences are replaced with U+FFFD, matching encoding/json.
Example ¶
package main
import (
"fmt"
"github.com/ubyte-source/go-jsonfast"
)
func main() {
escaped := jsonfast.EscapeString(`She said "hello" and left`)
fmt.Println(escaped)
}
Output: She said \"hello\" and left
func FindField ¶
FindField returns the raw value bytes for the first top-level field matching key, or (nil, false) if not found. Keys with JSON escape sequences are decoded on the fly. The scan stops at the first match, so malformed content after it is not detected.
Example ¶
package main
import (
"fmt"
"github.com/ubyte-source/go-jsonfast"
)
func main() {
data := []byte(`{"hostname":"fw01","severity":4,"message":"ok"}`)
if val, ok := jsonfast.FindField(data, "severity"); ok {
fmt.Printf("severity=%s\n", val)
}
}
Output: severity=4
func FindFieldString ¶ added in v0.0.2
FindFieldString is FindField with a string input. The returned slice aliases s and must not be mutated.
func FlattenMap ¶
FlattenMap flattens a nested map into outer.inner → value form. If dst is nil a new map is allocated sized to the total entry count.
Example ¶
package main
import (
"fmt"
"github.com/ubyte-source/go-jsonfast"
)
func main() {
m := map[string]map[string]string{
"host": {"name": "fw01", "ip": "10.0.0.1"},
}
flat := jsonfast.FlattenMap(m, nil)
fmt.Println(flat["host.name"])
fmt.Println(flat["host.ip"])
}
Output: fw01 10.0.0.1
func FlattenObject ¶
FlattenObject recursively flattens a JSON object's leaves into b (up to 64 levels deep), discarding the enclosing keys: nested leaves are emitted under their own names, so colliding leaf names produce duplicate keys. Non-object input is skipped silently; trailing content after the object is rejected.
func IsStructuralJSON ¶ added in v0.1.0
IsStructuralJSON reports whether s is a grammar-valid JSON object or array (every nested value checked) with no trailing content. Duplicate keys are not rejected; invalid UTF-8 bytes are passed through.
Example ¶
package main
import (
"fmt"
"github.com/ubyte-source/go-jsonfast"
)
func main() {
fmt.Println(jsonfast.IsStructuralJSON(`{"key":"value"}`))
fmt.Println(jsonfast.IsStructuralJSON(`not json`))
fmt.Println(jsonfast.IsStructuralJSON(`{not json}`))
}
Output: true false false
func IterateArray ¶ added in v0.0.2
IterateArray calls fn for each element; element is the raw JSON bytes of the value. It returns true only when data holds exactly one complete array (trailing whitespace allowed) and fn never returned false.
func IterateArrayString ¶ added in v0.0.2
IterateArrayString is IterateArray with a string input.
func IterateFields ¶
IterateFields calls fn for each top-level field of the JSON object in data. key includes the surrounding quotes; value is the raw JSON bytes. It returns true only when data holds exactly one complete object (trailing whitespace allowed) and fn never returned false. Values are structurally balanced but not grammar-validated; promote them with the Decode* helpers or check with IsStructuralJSON.
func IterateFieldsString ¶
IterateFieldsString is IterateFields with a string input. The slices passed to fn alias s and must not be mutated.
func IterateStringArray ¶ added in v0.0.2
IterateStringArray calls fn for each string element. val is the raw string body between the quotes: escape sequences are not decoded (use DecodeString on IterateArray elements when escapes may occur). val aliases the input and is only valid for the duration of the callback; use strings.Clone to retain. Non-string elements abort the iteration.
func IterateStringArrayString ¶ added in v0.0.2
IterateStringArrayString is IterateStringArray with a string input.
func Release ¶
func Release(b *Builder)
Release returns b to the pool. Buffers larger than 256 KB are discarded.
func ReleaseBatchWriter ¶
func ReleaseBatchWriter(bw *BatchWriter)
ReleaseBatchWriter returns bw to the pool. Buffers larger than 4 MB are discarded.
func SkipBracedAt ¶
SkipBracedAt skips a balanced opener/closer pair starting at data[i].
func SkipStringAt ¶
SkipStringAt skips a JSON string starting at data[i] (which must be '"') and returns the index past the closing quote. Raw control bytes (< 0x20) are rejected.
func SkipValueAt ¶
SkipValueAt skips one JSON value starting at data[i] and returns the index past it.
func WarmBatchWriterPool ¶ added in v0.1.0
func WarmBatchWriterPool(n int)
WarmBatchWriterPool pre-allocates n BatchWriters and returns them to the pool.
func WarmPool ¶ added in v0.1.0
func WarmPool(n int)
WarmPool pre-allocates n Builders and returns them to the pool.
Example ¶
package main
import (
"fmt"
"github.com/ubyte-source/go-jsonfast"
)
func main() {
jsonfast.WarmPool(128)
b := jsonfast.Acquire()
b.BeginObject()
b.AddStringField("warmed", "yes")
b.EndObject()
fmt.Println(string(b.Bytes()))
jsonfast.Release(b)
}
Output: {"warmed":"yes"}
Types ¶
type BatchWriter ¶
type BatchWriter struct {
// contains filtered or unexported fields
}
BatchWriter accumulates newline-delimited JSON records. Not safe for concurrent use.
Example ¶
package main
import (
"fmt"
"github.com/ubyte-source/go-jsonfast"
)
func main() {
bw := jsonfast.NewBatchWriter(256)
bw.Append([]byte(`{"line":1}`))
bw.Append([]byte(`{"line":2}`))
fmt.Print(string(bw.Bytes()))
}
Output: {"line":1} {"line":2}
func AcquireBatchWriter ¶
func AcquireBatchWriter() *BatchWriter
AcquireBatchWriter returns a BatchWriter from the pool.
Example ¶
package main
import (
"fmt"
"github.com/ubyte-source/go-jsonfast"
)
func main() {
bw := jsonfast.AcquireBatchWriter()
defer jsonfast.ReleaseBatchWriter(bw)
bw.Append([]byte(`{"pooled":true}`))
fmt.Print(string(bw.Bytes()))
}
Output: {"pooled":true}
func NewBatchWriter ¶
func NewBatchWriter(capacity int) *BatchWriter
NewBatchWriter returns a BatchWriter with the given initial capacity. Non-positive capacities are clamped to 4096.
func (*BatchWriter) Append ¶
func (w *BatchWriter) Append(record []byte)
Append writes record followed by '\n'.
func (*BatchWriter) AppendString ¶
func (w *BatchWriter) AppendString(record string)
AppendString writes record followed by '\n'.
func (*BatchWriter) Bytes ¶
func (w *BatchWriter) Bytes() []byte
Bytes returns the accumulated payload. The slice aliases the internal buffer.
func (*BatchWriter) Count ¶
func (w *BatchWriter) Count() int
Count returns the number of records in the batch.
func (*BatchWriter) Grow ¶
func (w *BatchWriter) Grow(n int)
Grow ensures at least n bytes of spare capacity.
func (*BatchWriter) Reset ¶
func (w *BatchWriter) Reset()
Reset clears the batch contents while retaining the backing array.
type Builder ¶
type Builder struct {
// contains filtered or unexported fields
}
Builder appends JSON into a reusable byte slice. The zero value is ready to use. Not safe for concurrent use.
Example ¶
package main
import (
"fmt"
"github.com/ubyte-source/go-jsonfast"
)
func main() {
b := jsonfast.New(256)
b.BeginObject()
b.AddStringField("message", "Hello, World!")
b.AddIntField("severity", 5)
b.AddBoolField("active", true)
b.EndObject()
fmt.Println(string(b.Bytes()))
}
Output: {"message":"Hello, World!","severity":5,"active":true}
Example (WithTimestamp) ¶
package main
import (
"fmt"
"time"
"github.com/ubyte-source/go-jsonfast"
)
func main() {
b := jsonfast.New(256)
ts := time.Date(2024, 3, 15, 14, 30, 45, 0, time.UTC)
b.BeginObject()
b.AddStringField("msg", "test")
b.AddTimeRFC3339Field("ts", ts)
b.EndObject()
fmt.Println(string(b.Bytes()))
}
Output: {"msg":"test","ts":"2024-03-15T14:30:45Z"}
func Acquire ¶
func Acquire() *Builder
Acquire returns a Builder from the pool, ready for use.
Example ¶
package main
import (
"fmt"
"github.com/ubyte-source/go-jsonfast"
)
func main() {
b := jsonfast.Acquire()
defer jsonfast.Release(b)
b.BeginObject()
b.AddStringField("source", "pool")
b.EndObject()
fmt.Println(string(b.Bytes()))
}
Output: {"source":"pool"}
func New ¶
New returns a Builder with the given initial capacity. Non-positive capacities are clamped to 256.
func (*Builder) AddBoolField ¶
AddBoolField adds "name":true or "name":false.
func (*Builder) AddBoolFieldKey ¶
AddBoolFieldKey adds "name":true or "name":false using a pre-computed key.
func (*Builder) AddFlattenedMapField ¶
AddFlattenedMapField emits nested map data as sorted "outer.inner":"value" fields into the current object. Up to 16 total entries fit in a stack buffer; larger maps allocate once.
func (*Builder) AddFloat64Field ¶
AddFloat64Field adds "name":<float64>. NaN and ±Inf are emitted as null.
Example ¶
package main
import (
"fmt"
"github.com/ubyte-source/go-jsonfast"
)
func main() {
b := jsonfast.New(64)
b.BeginObject()
b.AddFloat64Field("pi", 3.14159)
b.EndObject()
fmt.Println(string(b.Bytes()))
}
Output: {"pi":3.14159}
func (*Builder) AddFloat64FieldKey ¶
AddFloat64FieldKey adds "name":<float64> using a pre-computed key.
func (*Builder) AddInt64Field ¶
AddInt64Field adds "name":<int64>.
func (*Builder) AddInt64FieldKey ¶
AddInt64FieldKey adds "name":<int64> using a pre-computed key.
func (*Builder) AddIntField ¶
AddIntField adds "name":<int>.
func (*Builder) AddIntFieldKey ¶
AddIntFieldKey adds "name":<int> using a pre-computed key.
func (*Builder) AddNestedStringMapField ¶
AddNestedStringMapField adds "name":{outer:{inner:"v"}}, keys sorted at both levels.
func (*Builder) AddNullField ¶
AddNullField adds "name":null.
func (*Builder) AddNullFieldKey ¶
AddNullFieldKey adds "name":null using a pre-computed key.
func (*Builder) AddRawBytesField ¶
AddRawBytesField adds "name":<value> where name and value are copied verbatim.
func (*Builder) AddRawJSONField ¶
AddRawJSONField adds "name":<raw> without escaping. rawJSON must be well-formed JSON.
func (*Builder) AddRawJSONFieldKey ¶
AddRawJSONFieldKey adds "name":<raw> using a pre-computed key.
func (*Builder) AddStringArrayField ¶ added in v0.2.0
AddStringArrayField adds "name":["v1","v2",...] with value escaping.
func (*Builder) AddStringArrayFieldKey ¶ added in v0.2.0
AddStringArrayFieldKey adds "name":["v1",...] using a pre-computed key.
func (*Builder) AddStringField ¶
AddStringField adds "name":"value" with value escaping.
func (*Builder) AddStringFieldKey ¶
AddStringFieldKey adds "name":"value" using a pre-computed key.
func (*Builder) AddStringMapObject ¶
AddStringMapObject writes m as a standalone JSON object (no name, no leading comma). Keys are sorted. If rawJSONKey is non-empty, values for that key are embedded as raw JSON when IsStructuralJSON accepts them.
func (*Builder) AddStringMapObjectField ¶ added in v0.2.0
AddStringMapObjectField adds "name":{...} with m encoded as a JSON object. Semantics match AddStringMapObject.
func (*Builder) AddTimeRFC3339Field ¶
AddTimeRFC3339Field adds "name":"<RFC3339>" in UTC. Years are clamped to [0, 9999]; pre-epoch timestamps are clamped to the epoch.
func (*Builder) AddTimeRFC3339FieldKey ¶
AddTimeRFC3339FieldKey adds "name":"<RFC3339>" in UTC using a pre-computed key.
func (*Builder) AddTimeRFC3339OffsetField ¶ added in v0.2.0
AddTimeRFC3339OffsetField adds "name":"<RFC3339>" preserving the input timezone offset (Z, +HH:MM, or -HH:MM). Offsets are truncated to whole minutes (RFC 3339 cannot express seconds). If a negative offset would require a pre-epoch wall date, the UTC form of the same instant is emitted instead.
func (*Builder) AddTimeRFC3339OffsetFieldKey ¶ added in v0.2.0
AddTimeRFC3339OffsetFieldKey adds "name":"<RFC3339>" using a pre-computed key. Semantics match AddTimeRFC3339OffsetField.
func (*Builder) AddUint64Field ¶
AddUint64Field adds "name":<uint64>.
func (*Builder) AddUint64FieldKey ¶
AddUint64FieldKey adds "name":<uint64> using a pre-computed key.
func (*Builder) AppendEscapedString ¶
AppendEscapedString appends s with JSON escaping (no surrounding quotes).
func (*Builder) AppendRawString ¶
AppendRawString appends a raw string.
func (*Builder) BeginObject ¶
func (b *Builder) BeginObject()
BeginObject writes '{' and resets the field separator state.
func (*Builder) BeginObjectField ¶ added in v0.2.0
BeginObjectField opens a nested object as "name":{, ready for inner Add*Field calls. Pair with EndObjectField.
func (*Builder) BeginObjectFieldKey ¶ added in v0.2.0
BeginObjectFieldKey opens a nested object with a pre-computed key.
func (*Builder) Bytes ¶
Bytes returns the accumulated bytes. The slice aliases the internal buffer and must not be used after Reset or Release.
func (*Builder) EndObjectField ¶ added in v0.2.0
func (b *Builder) EndObjectField()
EndObjectField closes a nested object opened by BeginObjectField or BeginObjectFieldKey and restores the outer separator state.
func (*Builder) Grow ¶
Grow ensures at least n bytes of spare capacity.
Example ¶
package main
import (
"fmt"
"github.com/ubyte-source/go-jsonfast"
)
func main() {
b := jsonfast.New(16)
b.Grow(1024)
b.BeginObject()
b.AddStringField("k", "v")
b.EndObject()
fmt.Println(string(b.Bytes()))
}
Output: {"k":"v"}
func (*Builder) Reset ¶
func (b *Builder) Reset()
Reset clears the buffer contents while retaining the backing array.
type FieldKey ¶
type FieldKey string
FieldKey is a pre-computed field prefix in the form `,"name":`. Construct via NewFieldKey or as a typed string literal matching that layout exactly; the methods index into it and panic on malformed or empty keys.
func NewFieldKey ¶
NewFieldKey returns a FieldKey for the given name. The name must be safe ASCII (printable, excluding '"' and '\\'); NewFieldKey panics otherwise, since the prefix is cached verbatim and an unsafe name would silently corrupt every object that uses it. Call at init time, like regexp.MustCompile.
Example ¶
package main
import (
"fmt"
"github.com/ubyte-source/go-jsonfast"
)
func main() {
var keyLevel = jsonfast.NewFieldKey("level")
b := jsonfast.New(64)
b.BeginObject()
b.AddIntFieldKey(keyLevel, 3)
b.EndObject()
fmt.Println(string(b.Bytes()))
}
Output: {"level":3}