Documentation
¶
Overview ¶
Package jsonfast provides a zero-allocation JSON builder for fixed schemas.
Designed for ultra-high-throughput pipelines where every byte and every nanosecond matters. All methods operate on a reusable byte buffer; steady-state operation requires zero heap allocations on the documented hot paths: Builder, BatchWriter, every Add*Field method, string escape, the scan package (SkipValueAt, SkipStringAt, SkipBracedAt, IterateFields, IterateArray, IterateStringArray, FindField, FlattenObject, IsStructuralJSON), and AddNestedStringMapField and AddFlattenedMapField within their documented stack-buffer bounds.
Usage ¶
b := jsonfast.Acquire()
b.BeginObject()
b.AddStringField("message", "hello world")
b.AddIntField("severity", 3)
b.AddTimeRFC3339Field("timestamp", time.Now())
b.EndObject()
data := b.Bytes() // no allocation
jsonfast.Release(b) // return to pool
Pre-computed field keys ¶
For static field names known at init time, use FieldKey to eliminate the per-call quoting overhead:
var keyMessage = jsonfast.NewFieldKey("message")
b.AddStringFieldKey(keyMessage, "hello world")
Field name requirements ¶
Field names passed to Add*Field (string-keyed variants) and to NewFieldKey must be safe ASCII: bytes in the printable ASCII range (0x20–0x7E) excluding '"' and '\\'. They are copied verbatim into the output. For dynamic or untrusted names, escape them via EscapeString before use, or compose the output with AppendEscapedString / AppendRawString directly.
Scanning and parsing ¶
IterateFields, FindField, IterateArray and related functions parse JSON structurally without building an intermediate DOM. All accept []byte or string inputs (the *String variants use unsafe.Slice to avoid the conversion allocation). IterateStringArray and its String variant pass a zero-allocation string view to the callback; the view is only valid for the duration of the callback. Clone via strings.Clone to retain.
NDJSON batching ¶
BatchWriter appends complete JSON records separated by newlines. It implements io.Writer (each Write adds one record) and io.WriterTo (payload flush). AcquireBatchWriter / ReleaseBatchWriter manage a sync.Pool; WarmBatchWriterPool pre-seeds the pool.
Index ¶
- 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) AddRawJSONFieldKeyString(k FieldKey, rawJSON string)
- func (b *Builder) AddRawJSONFieldString(name, rawJSON 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) AddTimeRFC3339Field(name string, t time.Time)
- func (b *Builder) AddTimeRFC3339FieldKey(k FieldKey, t time.Time)
- func (b *Builder) AddUint8Field(name string, v uint8)
- func (b *Builder) AddUint16Field(name string, v uint16)
- func (b *Builder) AddUint32Field(name string, v uint32)
- 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) Bytes() []byte
- func (b *Builder) EndObject()
- 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 EscapeString ¶
EscapeString returns s with JSON special characters escaped per RFC 8259. If no escaping is needed (pure safe ASCII), s is returned unchanged (zero allocation). Invalid UTF-8 bytes are replaced with U+FFFD, matching the escape behavior used by Builder field methods.
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 looks up a top-level field by key in a JSON object. Returns the raw value bytes and true on match, or (nil, false) if not found or the JSON is malformed. Implemented as a direct scanner without a callback so the match-comparison and early-return are inlined.
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 over a string input, avoiding the []byte conversion allocation. The returned slice aliases the string's backing memory and must not be mutated.
func FlattenMap ¶
FlattenMap flattens a nested map[string]map[string]string into a flat map[string]string using dot-notation keys: outer.inner → value.
If dst is nil, a new map with an exact capacity hint is allocated. If dst is non-nil and has sufficient capacity for all flattened keys, the only allocation is one string per entry (for the dot-joined key), driven by the map insertion itself.
Example:
nested := map[string]map[string]string{
"sd@123": {"key1": "val1", "key2": "val2"},
}
flat := jsonfast.FlattenMap(nested, nil)
// flat == {"sd@123.key1": "val1", "sd@123.key2": "val2"}
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 nested JSON object into the Builder's current object. Nested objects are recursed up to 64 levels deep; leaf values are emitted as top-level fields. Non-object input is silently skipped. Returns false if the JSON is malformed or the nesting exceeds the depth limit.
func IsStructuralJSON ¶ added in v0.1.0
IsStructuralJSON reports whether s is a structurally valid JSON object or array with no trailing content. Grammar is validated in a single pass: objects require well-formed key:value pairs with commas; arrays require well-formed values with commas. Zero allocation.
Does NOT validate semantic correctness (e.g., duplicate keys, number formats, escape sequences beyond framing).
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}`)) // structurally invalid
}
Output: true false false
func IterateArray ¶ added in v0.0.2
IterateArray calls fn for each element in a JSON array. element is the raw JSON bytes of each value (string with quotes, number, bool, null, nested object, nested array). Returns false if the JSON is malformed or fn returns false.
func IterateArrayString ¶ added in v0.0.2
IterateArrayString is IterateArray over a string, avoiding the []byte conversion. The slice passed to fn aliases the string's backing memory and must not be mutated.
func IterateFields ¶
IterateFields calls fn for each top-level key-value pair in a JSON object. key includes the surrounding quotes; value is the raw JSON bytes. Returns false if the JSON is malformed or fn returns false.
func IterateFieldsString ¶
IterateFieldsString is IterateFields over a string, avoiding the []byte conversion. The slices passed to fn alias the string's backing memory and must not be mutated.
func IterateStringArray ¶ added in v0.0.2
IterateStringArray calls fn for each string element in a JSON array. val is a zero-allocation view aliasing the input; it is only valid for the duration of the callback. Use strings.Clone(val) in fn to retain the value beyond the call.
Non-string elements cause the iteration to abort and return false.
func IterateStringArrayString ¶ added in v0.0.2
IterateStringArrayString is IterateStringArray over a string input. See IterateStringArray for val lifetime rules.
func Release ¶
func Release(b *Builder)
Release returns the Builder to the pool for reuse. The Builder must not be used after calling Release. Buffers larger than 256 KB are discarded to bound memory retention.
func ReleaseBatchWriter ¶
func ReleaseBatchWriter(bw *BatchWriter)
ReleaseBatchWriter returns the BatchWriter to the pool for reuse. The writer must not be used after calling ReleaseBatchWriter. Buffers larger than 4 MB are discarded.
func SkipBracedAt ¶
SkipBracedAt skips a balanced delimiter pair starting at data[i]. Delegates string scanning to SkipStringAt for SWAR throughput. For non-string, non-bracket content, uses SWAR to skip 8 bytes at a time.
func SkipStringAt ¶
SkipStringAt finds the end of a JSON string starting at data[i]. data[i] must be '"'. Returns the index past the closing quote. Rejects raw control chars (< 0x20) per RFC 8259.
func SkipValueAt ¶
SkipValueAt skips a complete JSON value starting at data[i]. Returns the index past the value and true, or (i, false) on error.
func SkipWS ¶
SkipWS returns the index of the first non-whitespace byte at or after i. Uses a 256-byte lookup table for branch-free classification.
func WarmBatchWriterPool ¶ added in v0.1.0
func WarmBatchWriterPool(n int)
WarmBatchWriterPool pre-allocates n BatchWriter instances and returns them to the pool. Use before entering the hot path to smooth tail latency during warm-up. Values of n ≤ 0 are a no-op.
func WarmPool ¶ added in v0.1.0
func WarmPool(n int)
WarmPool pre-allocates n Builder instances and returns them to the pool. Use before entering the hot path to smooth tail latency during warm-up. Values of n ≤ 0 are a no-op.
Example ¶
package main
import (
"fmt"
"github.com/ubyte-source/go-jsonfast"
)
func main() {
// Pre-seed the pool before the hot path begins. Only the first
// Acquire after program start touches the allocator; subsequent
// Acquire/Release pairs are zero-alloc.
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 (NDJSON) records into a reusable buffer. Each record is a complete JSON object followed by '\n'. A BatchWriter is not safe for concurrent use.
Usage:
bw := jsonfast.NewBatchWriter(4096)
b := jsonfast.Acquire()
b.BeginObject()
b.AddStringField("msg", "hello")
b.EndObject()
bw.Append(b.Bytes())
jsonfast.Release(b)
payload := bw.Bytes()
bw.Reset()
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, ready for use. Call ReleaseBatchWriter when done to return it for reuse.
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 creates a BatchWriter with the given initial capacity. A non-positive capacity is clamped to 4096 bytes.
func (*BatchWriter) Append ¶
func (w *BatchWriter) Append(record []byte)
Append adds a complete JSON record followed by '\n' to the batch.
func (*BatchWriter) AppendString ¶
func (w *BatchWriter) AppendString(record string)
AppendString adds a complete JSON record (as string) followed by '\n' to the batch.
func (*BatchWriter) Bytes ¶
func (w *BatchWriter) Bytes() []byte
Bytes returns the accumulated NDJSON payload. The returned slice aliases the internal buffer; do not modify it.
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 the buffer has at least n bytes of spare capacity. Uses slices.Grow which leverages the runtime's optimized growslice.
func (*BatchWriter) Len ¶
func (w *BatchWriter) Len() int
Len returns the current byte length of the batch.
func (*BatchWriter) Reset ¶
func (w *BatchWriter) Reset()
Reset clears the batch for reuse without releasing memory.
type Builder ¶
type Builder struct {
// contains filtered or unexported fields
}
Builder is a minimal JSON builder that operates on a reusable byte slice. It avoids allocations by appending directly into the buffer. Not a fully general-purpose JSON writer; tailored for known field sets.
A Builder is 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. Call Release when done to return it for reuse.
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 creates a new Builder with the given initial capacity. A non-positive capacity is clamped to 256 bytes.
func (*Builder) AddBoolField ¶
AddBoolField adds a "name":true/false field.
func (*Builder) AddBoolFieldKey ¶
AddBoolFieldKey adds a "name":true/false field using a pre-computed key.
func (*Builder) AddFlattenedMapField ¶
AddFlattenedMapField writes nested map data as flattened "outer.inner":"value" fields directly into the JSON object being built, without materializing an intermediate flat map. Keys are sorted for deterministic output.
Zero allocation when the total number of flattened entries is at most 16 (stack-allocated sort buffer). Larger maps fall back to one heap allocation for the sort buffer.
func (*Builder) AddFloat64Field ¶
AddFloat64Field adds a "name":<float64> field. NaN and ±Inf are emitted as null because JSON (RFC 8259) does not represent non-finite numbers. Integer values are emitted without a decimal point; fractional values use strconv.AppendFloat with full precision.
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 a "name":<float64> field using a pre-computed key. See AddFloat64Field for NaN/Inf handling.
func (*Builder) AddInt64Field ¶
AddInt64Field adds a "name":<int64> field.
func (*Builder) AddInt64FieldKey ¶
AddInt64FieldKey adds a "name":<int64> field using a pre-computed key.
func (*Builder) AddIntField ¶
AddIntField adds a "name":<int> field.
func (*Builder) AddIntFieldKey ¶
AddIntFieldKey adds a "name":<int> field using a pre-computed key.
func (*Builder) AddNestedStringMapField ¶
AddNestedStringMapField adds a "name":{outer:{inner:"v",...},...} field with keys sorted at both levels for deterministic output.
Zero allocation when every map (outer and each inner) has at most 8 keys; larger maps fall back to one heap allocation per oversized map for the sort buffer.
func (*Builder) AddNullField ¶
AddNullField adds a "name":null field.
func (*Builder) AddNullFieldKey ¶
AddNullFieldKey adds a "name":null field using a pre-computed key.
func (*Builder) AddRawBytesField ¶
AddRawBytesField adds a "name":<value> field where name is raw bytes (no quoting or escaping is performed) and value is raw JSON bytes.
func (*Builder) AddRawJSONField ¶
AddRawJSONField adds a "name":<raw json> field without escaping. The caller must ensure rawJSON is well-formed JSON.
func (*Builder) AddRawJSONFieldKey ¶
AddRawJSONFieldKey adds a "name":<raw json> field using a pre-computed key.
func (*Builder) AddRawJSONFieldKeyString ¶
AddRawJSONFieldKeyString adds a "name":<raw json> field using a pre-computed key, taking the raw JSON as a string.
func (*Builder) AddRawJSONFieldString ¶
AddRawJSONFieldString is like AddRawJSONField but takes a string, avoiding the []byte conversion allocation at the call site.
func (*Builder) AddStringField ¶
AddStringField adds a "name":"value" field. The value is JSON-escaped.
func (*Builder) AddStringFieldKey ¶
AddStringFieldKey adds a "name":"value" field using a pre-computed key.
func (*Builder) AddStringMapObject ¶
AddStringMapObject writes m as a JSON object {...} at the current position. Keys are sorted for deterministic output. If rawJSONKey is non-empty, values for that key are embedded as raw JSON when they pass structural validation via IsStructuralJSON. This method does not add a field name — it writes just the object.
Zero allocation for maps with at most 8 keys (stack-allocated sort buffer).
func (*Builder) AddTimeRFC3339Field ¶
AddTimeRFC3339Field adds a "name":"<RFC3339>" field. The output is always in UTC regardless of the input timezone because computation uses t.Unix() (timezone-independent). Negative timestamps are clamped to the epoch; years beyond 9999 are clamped to 9999.
func (*Builder) AddTimeRFC3339FieldKey ¶
AddTimeRFC3339FieldKey adds a "name":"<RFC3339>" field in UTC using a pre-computed key. See AddTimeRFC3339Field for clamping semantics.
func (*Builder) AddUint8Field ¶
AddUint8Field adds a "name":<uint8> field.
func (*Builder) AddUint16Field ¶
AddUint16Field adds a "name":<uint16> field.
func (*Builder) AddUint32Field ¶
AddUint32Field adds a "name":<uint32> field.
func (*Builder) AddUint64Field ¶
AddUint64Field adds a "name":<uint64> field.
func (*Builder) AddUint64FieldKey ¶
AddUint64FieldKey adds a "name":<uint64> field using a pre-computed key.
func (*Builder) AppendEscapedString ¶
AppendEscapedString appends s with JSON special characters escaped directly into the buffer (no enclosing quotes).
func (*Builder) AppendRawString ¶
AppendRawString appends a raw string to the buffer without escaping or framing.
func (*Builder) BeginObject ¶
func (b *Builder) BeginObject()
BeginObject writes '{' and resets the field separator state.
func (*Builder) Bytes ¶
Bytes returns the accumulated bytes. The returned slice aliases the internal buffer; do not modify it, and do not use it after Reset or Release.
func (*Builder) Grow ¶
Grow ensures the buffer has at least n bytes of spare capacity. Uses slices.Grow which leverages the runtime's optimized growslice.
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 Builder for reuse. The underlying buffer is retained.
type FieldKey ¶
type FieldKey string
FieldKey is a pre-computed JSON field key prefix that stores `,"name":`. The non-comma form is obtained by slicing the first byte. Instances should be constructed via NewFieldKey, or as typed string literals matching the documented layout (`,"name":` where name is safe ASCII).
func NewFieldKey ¶
NewFieldKey returns a pre-computed key for the given name. The name must be safe ASCII (no escaping required). Call at init time, not on the hot path; the returned value can be stored in a package-level var.
Example ¶
package main
import (
"fmt"
"github.com/ubyte-source/go-jsonfast"
)
func main() {
// Lift static field names out of the hot path.
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}