jsonfast

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 20, 2026 License: MIT Imports: 8 Imported by: 0

README

go-jsonfast

A zero-allocation JSON builder and scanner for Go, tailored for fixed schemas and ultra-high-throughput pipelines.

Go Version Go Reference Zero Dependencies

Features

  • Zero-allocation Builder with Acquire / Release pool and WarmPool for pre-seeding.
  • Pre-computed FieldKey for static field names (typed string, literal-constructible).
  • io.Writer / io.WriterTo on both Builder and BatchWriter for pipeline interop.
  • NDJSON BatchWriter with pool, pre-seed, and write sinks.
  • SWAR-accelerated string escape and scanner (SkipValueAt, SkipStringAt, SkipBracedAt).
  • Direct-scan FindField, IterateFields, IterateArray, IterateStringArray (zero-alloc borrow).
  • FlattenObject / AddFlattenedMapField for dot-notation flattening.
  • RFC 3339 time formatting without time.Format allocations, UTC-normalised.
  • RFC 8259 compliance: invalid UTF-8 → U+FFFD; NaN / ±Inf → null.
  • IsStructuralJSON single-pass validator for untrusted payloads.
  • testing.AllocsPerRun CI gate: every hot path is asserted zero-alloc.
  • Native Go fuzzers for every scanner and validator.
  • Profile-guided optimisation: default.pgo tracked; make pgo regenerates.

Requires Go 1.25+.

Quick start

package main

import (
    "fmt"
    "time"

    "github.com/ubyte-source/go-jsonfast"
)

func main() {
    b := jsonfast.Acquire()
    defer jsonfast.Release(b)

    b.BeginObject()
    b.AddStringField("message", "hello world")
    b.AddIntField("severity", 3)
    b.AddTimeRFC3339Field("timestamp", time.Now())
    b.EndObject()

    fmt.Println(string(b.Bytes()))
}
Reuse without allocation
b := jsonfast.New(512)

b.BeginObject()
b.AddStringField("k", "v1")
b.EndObject()
process(b.Bytes())

b.Reset() // ready for reuse, no allocation
b.BeginObject()
b.AddStringField("k", "v2")
b.EndObject()
process(b.Bytes())
NDJSON batching
bw := jsonfast.AcquireBatchWriter()
defer jsonfast.ReleaseBatchWriter(bw)

b := jsonfast.Acquire()
defer jsonfast.Release(b)

for _, msg := range messages {
    b.Reset()
    b.BeginObject()
    b.AddStringField("msg", msg)
    b.EndObject()
    bw.Append(b.Bytes())
}

// bw.Bytes() is the NDJSON payload; stream via io.WriterTo:
if _, err := bw.WriteTo(conn); err != nil {
    return err
}
Pre-computed field keys

Static field names known at init time can be lifted to FieldKey to eliminate the per-call quoting. Construct via NewFieldKey or as a typed string literal:

var (
    keyMessage  = jsonfast.NewFieldKey("message")
    keySeverity = jsonfast.NewFieldKey("severity")

    // Equivalent literal form (matching the documented layout):
    keyVersion jsonfast.FieldKey = `,"version":`
)

b := jsonfast.Acquire()
b.BeginObject()
b.AddStringFieldKey(keyMessage, "hello world")
b.AddIntFieldKey(keySeverity, 3)
b.AddIntFieldKey(keyVersion, 1)
b.EndObject()
jsonfast.Release(b)
Warm-up before the hot path

Pre-seed the pools to smooth tail latency at start-up:

jsonfast.WarmPool(128)
jsonfast.WarmBatchWriterPool(32)
Map flattening
nested := map[string]map[string]string{
    "sd@123": {"key1": "val1", "key2": "val2"},
}
flat := jsonfast.FlattenMap(nested, nil)
// flat["sd@123.key1"] == "val1"
Structural validation
if !jsonfast.IsStructuralJSON(payload) {
    return fmt.Errorf("not a structurally valid JSON object/array")
}

API overview

See the package documentation for the full godoc.

Builder
Function Description
New(capacity int) *Builder New builder with initial capacity (default 256).
Acquire() *Builder Take a builder from the pool.
Release(*Builder) Return a builder to the pool.
WarmPool(n int) Pre-seed the pool with n builders.
(*Builder).Reset() Clear for reuse; buffer retained.
(*Builder).Bytes() []byte Current payload (aliases internal buffer).
(*Builder).Len() int Current byte length.
(*Builder).Grow(n int) Ensure n spare bytes of capacity.
(*Builder).Write(p []byte) (int, error) io.Writer: append p unchanged.
(*Builder).WriteTo(w io.Writer) (int64, error) io.WriterTo: flush payload.
Field methods (string-keyed)
Method Output
BeginObject() / EndObject() { / }
AddStringField(name, value) "name":"value" with escaping
AddIntField(name, v int) "name":123
AddInt64Field(name, v int64) "name":123
AddUint8Field / AddUint16Field / AddUint32Field / AddUint64Field "name":n
AddFloat64Field(name, v float64) "name":3.14 — NaN/±Inf → null
AddBoolField(name, v bool) "name":true / "name":false
AddNullField(name) "name":null
AddTimeRFC3339Field(name, t time.Time) "name":"YYYY-MM-DDThh:mm:ss[.fffffffff]Z"
AddRawJSONField(name, rawJSON []byte) "name":<raw> — no escaping
AddRawJSONFieldString(name, rawJSON string) Same, string input
AddNestedStringMapField(name, m) "name":{outer:{inner:"v",...},...}, sorted
AddStringMapObject(m, rawJSONKey) writes a map[string]string as object
AddFlattenedMapField(m) flat "outer.inner":"value" fields, sorted

Field name requirement: safe ASCII (no escaping required). For untrusted names, escape via EscapeString or compose via AppendEscapedString + AppendRawString.

Pre-computed FieldKey methods
Function Notes
type FieldKey string Typed string holding ,"name":
NewFieldKey(name string) FieldKey Factory; call at init time
AddStringFieldKey / AddIntFieldKey / AddInt64FieldKey / AddUint64FieldKey
AddBoolFieldKey / AddFloat64FieldKey / AddNullFieldKey
AddTimeRFC3339FieldKey
AddRawJSONFieldKey / AddRawJSONFieldKeyString
Raw appenders
Method Description
AppendRaw([]byte) Append raw bytes, no escaping or framing
AppendRawString(string) Append raw string, no escaping or framing
AppendEscapedString(string) Append with JSON escaping, zero-alloc
AddRawBytesField(name, value []byte) "name":<value> with name as raw bytes
Scanner
Function Description
IterateFields(data []byte, fn) bool Callback for each "key":value pair.
IterateFieldsString(s string, fn) bool Same, string input.
FindField(data []byte, key string) ([]byte, bool) Direct lookup without callback.
FindFieldString(s, key string) ([]byte, bool) Same, string input.
IterateArray(data []byte, fn) bool Callback for each element.
IterateArrayString(s string, fn) bool Same, string input.
IterateStringArray(data []byte, fn func(val string) bool) bool Zero-alloc borrow; see lifetime note.
IterateStringArrayString(s string, fn) bool Same, string input.
FlattenObject(b *Builder, data []byte) bool Recursive flatten into builder (≤ 64 levels).
SkipWS / SkipValueAt / SkipStringAt / SkipBracedAt Low-level SWAR scanners.
IsStructuralJSON(s string) bool Single-pass grammar validator with trailing-content rejection.
EscapeString(s string) string Returns s unchanged if already safe; allocates only when escape needed.

IterateStringArray passes a zero-allocation string view aliasing the input data. The view is only valid for the duration of the callback — clone via strings.Clone to retain.

BatchWriter (NDJSON)
Function Description
NewBatchWriter(capacity int) *BatchWriter New writer with initial capacity (default 4096).
AcquireBatchWriter() / ReleaseBatchWriter(*BatchWriter) Pool API.
WarmBatchWriterPool(n int) Pre-seed the pool.
(*BatchWriter).Append([]byte) / .AppendString(string) Record + newline.
(*BatchWriter).Write(p []byte) (int, error) io.Writer: one record per call.
(*BatchWriter).WriteTo(w io.Writer) (int64, error) io.WriterTo: flush payload.
(*BatchWriter).Bytes() / .Len() / .Count() / .Reset() / .Grow(n) Buffer management.
FlattenMap
Function Description
FlattenMap(m, dst map[string]string) map[string]string Materialise a flat map; pass dst to avoid re-allocating.

Benchmarks

Measured on a 32-core x86-64 server, Go 1.25.9, with default.pgo enabled.

BenchmarkBuilder_FullSyslogObject-32             160 ns/op     0 B/op    0 allocs/op
BenchmarkBuilder_FullSyslogObject_FieldKey-32    150 ns/op     0 B/op    0 allocs/op
BenchmarkBuilder_EscapeString_PureASCII-32        34 ns/op     0 B/op    0 allocs/op
BenchmarkBuilder_NestedStringMapField-32         378 ns/op     0 B/op    0 allocs/op
BenchmarkBuilder_AcquireRelease-32                32 ns/op     0 B/op    0 allocs/op
BenchmarkIterateFields-32                        140 ns/op     0 B/op    0 allocs/op
BenchmarkFindField-32                            104 ns/op     0 B/op    0 allocs/op
BenchmarkIterateArray_Strings100-32             1300 ns/op     0 B/op    0 allocs/op
BenchmarkParallel_FindField-32                   7.4 ns/op  11 GB/s      0 allocs/op
BenchmarkParallel_EscapeString_PureASCII-32      1.9 ns/op  60 GB/s      0 allocs/op

Run with:

make bench        # sequential benchmarks
make parallel     # RunParallel benchmarks across CPU counts

Testing and gates

make test       # all tests with race detector
make alloc      # zero-allocation assertions (AllocsPerRun)
make fuzz       # native Go fuzzing, 30s per target (override with FUZZTIME=10m)
make cover      # HTML coverage report
make lint       # staticcheck -checks=all + golangci-lint (0 issues, 0 nolint for complexity)
make pgo        # regenerate default.pgo
make ci         # vet + lint + test + alloc + cover + bench

Operational limits

Parameter Value Rationale
Builder pool max retained capacity 256 KiB Bounds per-instance retention.
BatchWriter pool max retained capacity 4 MiB Bounds per-instance retention for batch sinks.
FlattenObject maximum depth 64 Defends against pathological nesting.
AddNestedStringMapField stack buffer 8 keys (both levels) Zero-alloc path; larger maps get one heap buffer per oversized map.
AddFlattenedMapField stack buffer 16 total entries Zero-alloc path; larger maps get one heap buffer.
EscapeString stack buffer 506 bytes after escape margin Zero-alloc for the common case.

Design principles

  1. Zero-alloc is a constraint, not a goal. Every hot path is gated by testing.AllocsPerRun; a regression fails CI.
  2. Fixed schemas only. This is not a general-purpose JSON encoder.
  3. Deterministic output. Keys are sorted wherever the caller may dedupe or cache the output.
  4. No dependencies. Pure Go, no cgo, no code generation.
  5. The buffer is the API. All writes land in []byte; no intermediate representations.

Project layout

go-jsonfast/
├── jsonfast.go        # Builder: pool, field methods, escape, time, integer formatting
├── scan.go            # Scanner: Skip*, Iterate*, FindField, FlattenObject, IsStructuralJSON
├── swar.go            # SWAR constants and byte-classification helpers
├── flatten.go         # FlattenMap and AddFlattenedMapField
├── ndjson.go          # BatchWriter: pool, append, io.Writer / io.WriterTo
├── doc.go             # Package documentation
├── alloc_test.go      # Zero-allocation CI gate (testing.AllocsPerRun)
├── parallel_test.go   # RunParallel benchmarks for pool-contention profiling
├── io_test.go         # io.Writer / io.WriterTo interface contract
├── fuzz_test.go       # Native Go fuzzers for scanners and validators
├── *_test.go          # Unit tests, example tests
├── default.pgo        # Profile-guided optimisation profile
├── .golangci.yml      # Strict linter configuration
└── Makefile           # test, bench, fuzz, lint, cover, pgo, ci

Contributing

See CONTRIBUTING.md. For security issues, see SECURITY.md.

Versioning

We use SemVer. Releases are tracked in the repository tags.

Authors

See also the list of contributors.

License

MIT — see LICENSE.

Support

If go-jsonfast is useful for your pipelines, consider supporting the work:

Buy Me A Coffee

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

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func EscapeString

func EscapeString(s string) string

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

func FindField(data []byte, key string) ([]byte, bool)

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

func FindFieldString(s, key string) ([]byte, bool)

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

func FlattenMap(m map[string]map[string]string, dst map[string]string) map[string]string

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

func FlattenObject(b *Builder, data []byte) bool

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

func IsStructuralJSON(s string) bool

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

func IterateArray(data []byte, fn func(element []byte) bool) bool

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

func IterateArrayString(s string, fn func(element []byte) bool) bool

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

func IterateFields(data []byte, fn func(key, value []byte) bool) bool

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

func IterateFieldsString(s string, fn func(key, value []byte) bool) bool

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

func IterateStringArray(data []byte, fn func(val string) bool) bool

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

func IterateStringArrayString(s string, fn func(val string) bool) bool

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

func SkipBracedAt(data []byte, i int, opener, closer byte) (int, bool)

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

func SkipStringAt(data []byte, i int) (int, bool)

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

func SkipValueAt(data []byte, i int) (int, bool)

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

func SkipWS(data []byte, i int) int

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.

func (*BatchWriter) Write added in v0.1.0

func (w *BatchWriter) Write(p []byte) (int, error)

Write implements io.Writer by appending p as a single NDJSON record (a newline is added). Write never returns an error.

func (*BatchWriter) WriteTo added in v0.1.0

func (w *BatchWriter) WriteTo(target io.Writer) (int64, error)

WriteTo implements io.WriterTo. The accumulated NDJSON payload is written to target in a single call. The batch state is unchanged.

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

func New(capacity int) *Builder

New creates a new Builder with the given initial capacity. A non-positive capacity is clamped to 256 bytes.

func (*Builder) AddBoolField

func (b *Builder) AddBoolField(name string, v bool)

AddBoolField adds a "name":true/false field.

func (*Builder) AddBoolFieldKey

func (b *Builder) AddBoolFieldKey(k FieldKey, v bool)

AddBoolFieldKey adds a "name":true/false field using a pre-computed key.

func (*Builder) AddFlattenedMapField

func (b *Builder) AddFlattenedMapField(m map[string]map[string]string)

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

func (b *Builder) AddFloat64Field(name string, v float64)

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

func (b *Builder) AddFloat64FieldKey(k FieldKey, v float64)

AddFloat64FieldKey adds a "name":<float64> field using a pre-computed key. See AddFloat64Field for NaN/Inf handling.

func (*Builder) AddInt64Field

func (b *Builder) AddInt64Field(name string, v int64)

AddInt64Field adds a "name":<int64> field.

func (*Builder) AddInt64FieldKey

func (b *Builder) AddInt64FieldKey(k FieldKey, v int64)

AddInt64FieldKey adds a "name":<int64> field using a pre-computed key.

func (*Builder) AddIntField

func (b *Builder) AddIntField(name string, v int)

AddIntField adds a "name":<int> field.

func (*Builder) AddIntFieldKey

func (b *Builder) AddIntFieldKey(k FieldKey, v int)

AddIntFieldKey adds a "name":<int> field using a pre-computed key.

func (*Builder) AddNestedStringMapField

func (b *Builder) AddNestedStringMapField(name string, m map[string]map[string]string)

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

func (b *Builder) AddNullField(name string)

AddNullField adds a "name":null field.

func (*Builder) AddNullFieldKey

func (b *Builder) AddNullFieldKey(k FieldKey)

AddNullFieldKey adds a "name":null field using a pre-computed key.

func (*Builder) AddRawBytesField

func (b *Builder) AddRawBytesField(name, value []byte)

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

func (b *Builder) AddRawJSONField(name string, rawJSON []byte)

AddRawJSONField adds a "name":<raw json> field without escaping. The caller must ensure rawJSON is well-formed JSON.

func (*Builder) AddRawJSONFieldKey

func (b *Builder) AddRawJSONFieldKey(k FieldKey, rawJSON []byte)

AddRawJSONFieldKey adds a "name":<raw json> field using a pre-computed key.

func (*Builder) AddRawJSONFieldKeyString

func (b *Builder) AddRawJSONFieldKeyString(k FieldKey, rawJSON string)

AddRawJSONFieldKeyString adds a "name":<raw json> field using a pre-computed key, taking the raw JSON as a string.

func (*Builder) AddRawJSONFieldString

func (b *Builder) AddRawJSONFieldString(name, rawJSON string)

AddRawJSONFieldString is like AddRawJSONField but takes a string, avoiding the []byte conversion allocation at the call site.

func (*Builder) AddStringField

func (b *Builder) AddStringField(name, value string)

AddStringField adds a "name":"value" field. The value is JSON-escaped.

func (*Builder) AddStringFieldKey

func (b *Builder) AddStringFieldKey(k FieldKey, value string)

AddStringFieldKey adds a "name":"value" field using a pre-computed key.

func (*Builder) AddStringMapObject

func (b *Builder) AddStringMapObject(m map[string]string, rawJSONKey string)

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

func (b *Builder) AddTimeRFC3339Field(name string, t time.Time)

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

func (b *Builder) AddTimeRFC3339FieldKey(k FieldKey, t time.Time)

AddTimeRFC3339FieldKey adds a "name":"<RFC3339>" field in UTC using a pre-computed key. See AddTimeRFC3339Field for clamping semantics.

func (*Builder) AddUint8Field

func (b *Builder) AddUint8Field(name string, v uint8)

AddUint8Field adds a "name":<uint8> field.

func (*Builder) AddUint16Field

func (b *Builder) AddUint16Field(name string, v uint16)

AddUint16Field adds a "name":<uint16> field.

func (*Builder) AddUint32Field

func (b *Builder) AddUint32Field(name string, v uint32)

AddUint32Field adds a "name":<uint32> field.

func (*Builder) AddUint64Field

func (b *Builder) AddUint64Field(name string, v uint64)

AddUint64Field adds a "name":<uint64> field.

func (*Builder) AddUint64FieldKey

func (b *Builder) AddUint64FieldKey(k FieldKey, v uint64)

AddUint64FieldKey adds a "name":<uint64> field using a pre-computed key.

func (*Builder) AppendEscapedString

func (b *Builder) AppendEscapedString(s string)

AppendEscapedString appends s with JSON special characters escaped directly into the buffer (no enclosing quotes).

func (*Builder) AppendRaw

func (b *Builder) AppendRaw(p []byte)

AppendRaw appends raw bytes to the buffer without escaping or framing.

func (*Builder) AppendRawString

func (b *Builder) AppendRawString(s string)

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

func (b *Builder) Bytes() []byte

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) EndObject

func (b *Builder) EndObject()

EndObject writes '}'.

func (*Builder) Grow

func (b *Builder) 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.

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) Len

func (b *Builder) Len() int

Len returns the current byte length of the buffer.

func (*Builder) Reset

func (b *Builder) Reset()

Reset clears the Builder for reuse. The underlying buffer is retained.

func (*Builder) Write added in v0.1.0

func (b *Builder) Write(p []byte) (int, error)

Write implements io.Writer. Bytes are appended unchanged to the buffer; Write never returns an error.

func (*Builder) WriteTo added in v0.1.0

func (b *Builder) WriteTo(w io.Writer) (int64, error)

WriteTo implements io.WriterTo. The accumulated bytes are written to w in a single call. The Builder state is unchanged.

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

func NewFieldKey(name string) FieldKey

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}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL