Documentation
¶
Overview ¶
Package zerojson is the runtime for a codegen-based JSON codec built for high-throughput, fixed-schema message shapes: zero-allocation encoding and near-zero-allocation decoding, with output byte-identical to easyjson so an existing wire format can be adopted and verified byte-for-byte. Installing it pulls no dependencies — the root module's go.mod has no requires.
You don't write codecs by hand or reflect at runtime. The generator (cmd/zerojsongen) reads your Go types once, at build time, and emits the codec as ordinary Go source.
Generate, then use ¶
Point the generator at a package and list the root types (nested and element/value structs are auto-discovered):
//go:generate go run github.com/neal/zerojson/cmd/zerojsongen -dir . -types Event,Record -out zerojson_gen.go
It writes AppendZJSON / UnmarshalZJSON / UnmarshalZJSONBorrow methods for each type. Roots may be structs, named slices (type X []T, []*T), or named string-keyed maps (type Y map[string]T).
buf, err := v.AppendZJSON(buf[:0]) // encode; append-style, reuse buf for zero alloc err := v.UnmarshalZJSON(data) // decode; strings are copied
AppendZJSON never resets dst — it appends and returns the extended slice. On error the returned buffer holds a partial encoding and must be discarded.
Three decode modes and their lifetimes ¶
The modes trade copying for aliasing; pick by how long the decoded value must outlive the input buffer.
UnmarshalZJSON(data): decoded strings are COPIED, so the value is independent of data — safe once data is reused or freed. Costs one arena allocation per call (backing the target's pointer fields).
UnmarshalZJSONBorrow(data): decoded strings (and zerojson.Raw fields, and pooled slice/map contents) ALIAS data. Zero-copy, but data must stay live and unmodified for the whole lifetime of the value. Never use it with a pooled or reused input buffer (e.g. a Kafka fetch buffer) unless lifetimes are pinned.
A pooled Decoder (generate with -pool): Decode / DecodeBorrow reuse a single arena across calls, eliminating the per-decode arena allocation — combined with DecodeBorrow, a decode loop allocates zero bytes. The catch is the arena is shared: the value holds pointers into it (and, in borrow mode, into data), so it is invalidated by the next Decode/DecodeBorrow on that Decoder. Finish with — or copy out of — one result before decoding the next. Pool one Decoder per goroutine, or via a sync.Pool.
JSON null clears pointer, slice, and map fields (and leaves value fields untouched), matching encoding/json on a reused target.
Valid: validate without decoding ¶
Valid(data) is a standalone, zero-allocation, full RFC 8259 grammar validator whose verdict matches encoding/json.Valid for every input. A successful decode already implies the payload was grammar-valid JSON (unknown fields and skipped content are grammar-checked, and trailing data is rejected), with one documented exception: a known field's own value stays leniently parsed. So Valid is for validate-WITHOUT-decode call sites — an opaque payload stored or forwarded without ever being decoded — which have no decode call to inherit that guarantee from.
Compatibility, in brief ¶
Encoded output is byte-identical to easyjson's, including its default HTML escaping. Keys are matched case-sensitively (like easyjson and encoding/json/v2). A handful of deliberate divergences from encoding/json v1 (float formatting, verbatim Marshaler output, lenient known-field values) and the single knowing divergence from easyjson (omitzero, which easyjson ignores) are enumerated in the README and pinned by differential tests.
Extending the fast path ¶
A type that formats as one JSON token can join the zero-alloc path without reflection two ways: implement the Appender/Reader interface pair on the type (best for types you own), or register free append/read functions with the generator's -leaf flag (for types you don't). zerojson.Raw captures and re-emits an arbitrary JSON value verbatim, for projection/passthrough decoding.
Correctness ¶
This runtime package is deliberately dependency-free; the differential and byte-identity test suites that need external oracles (easyjson, encoding/json v1 and v2, google/uuid) live in the separate conformance module. See github.com/neal/zerojson/conformance for the oracle-by-property story and the fuzzers that pin it.
Example (AppenderReader) ¶
Example_appenderReader shows a hand-written type joining the fast path via the Appender/Reader pair, round-tripped through the same append/read calling convention the generator emits.
package main
import (
"fmt"
"strconv"
"strings"
"github.com/neal/zerojson"
)
// Ratio joins the zero-alloc encode/decode path by implementing the
// Appender/Reader interface pair directly — no -leaf registration and no
// reflection. Any generated struct with a Ratio field picks up the fast
// path automatically.
type Ratio struct{ Num, Den int }
// AppendZJSON encodes r as "num/den". It follows the same contract as every
// generated AppendZJSON: append to dst, never reset it, return the extended
// slice.
func (r Ratio) AppendZJSON(dst []byte) ([]byte, error) {
dst = append(dst, '"')
dst = strconv.AppendInt(dst, int64(r.Num), 10)
dst = append(dst, '/')
dst = strconv.AppendInt(dst, int64(r.Den), 10)
return append(dst, '"'), nil
}
// ReadZJSON decodes "num/den" from l, reporting failures via l.Fail — the
// leaf read convention (pull a token, report errors on the Lexer, no return
// value).
func (r *Ratio) ReadZJSON(l *zerojson.Lexer) {
s := l.ReadString()
if l.Err() != nil {
return
}
num, den, ok := strings.Cut(s, "/")
n, err1 := strconv.Atoi(num)
d, err2 := strconv.Atoi(den)
if !ok || err1 != nil || err2 != nil {
l.Fail("ratio %q: invalid", s)
return
}
r.Num, r.Den = n, d
}
func main() {
enc, _ := Ratio{3, 4}.AppendZJSON(nil)
fmt.Println(string(enc))
var got Ratio
l := zerojson.NewLexer(enc, false)
got.ReadZJSON(&l)
fmt.Printf("%d/%d\n", got.Num, got.Den)
}
Output: "3/4" 3/4
Example (CustomLeaf) ¶
Example_customLeaf exercises a -leaf codec pair directly (the generator would emit calls to exactly these functions at each Color field).
package main
import (
"fmt"
"strconv"
"strings"
"github.com/neal/zerojson"
)
// appendColor / readColor are a custom-leaf codec pair for a type you don't
// own (here, a plain uint32 treated as an RGB color). Registering them with
// the generator —
//
// -leaf 'yourpkg.Color=yourpkg.appendColor,yourpkg.readColor'
//
// makes every Color field encode as "#rrggbb" on the fast path. The append
// function is infallible; the read function reports errors via the Lexer.
func appendColor(dst []byte, c *uint32) []byte {
return fmt.Appendf(dst, `"#%06x"`, *c&0xffffff)
}
func readColor(l *zerojson.Lexer, c *uint32) {
s := l.ReadString()
if l.Err() != nil {
return
}
v, err := strconv.ParseUint(strings.TrimPrefix(s, "#"), 16, 32)
if err != nil {
l.Fail("color %q: invalid", s)
return
}
*c = uint32(v)
}
func main() {
c := uint32(0x3366ff)
enc := appendColor(nil, &c)
fmt.Println(string(enc))
var got uint32
l := zerojson.NewLexer(enc, false)
readColor(&l, &got)
fmt.Printf("%#06x\n", got)
}
Output: "#3366ff" 0x3366ff
Example (Valid) ¶
Example_valid validates an untrusted payload at a trust boundary without decoding it — the use case Valid exists for (a payload stored or forwarded but never decoded has no decode call to inherit a grammar-validity guarantee from).
package main
import (
"fmt"
"github.com/neal/zerojson"
)
func main() {
trusted := []byte(`{"user":"alice","roles":["admin"]}`)
fmt.Println(zerojson.Valid(trusted) == nil)
malformed := []byte(`{"user":"alice",}`)
fmt.Println(zerojson.Valid(malformed) == nil)
}
Output: true false
Index ¶
- Variables
- func AppendBool(dst []byte, v bool) []byte
- func AppendBytesBase64(dst, src []byte) []byte
- func AppendDecimalParts(dst []byte, coeff int64, exp int32) []byte
- func AppendFloat32(dst []byte, f float32) ([]byte, error)
- func AppendFloat64(dst []byte, f float64) ([]byte, error)
- func AppendInt64(dst []byte, v int64) []byte
- func AppendInterface(dst []byte, v any) ([]byte, error)
- func AppendQuotedInt64(dst []byte, v int64) []byte
- func AppendQuotedUint64(dst []byte, v uint64) []byte
- func AppendRaw(dst []byte, r *Raw) []byte
- func AppendString(dst []byte, s string) []byte
- func AppendStringBytes(dst, b []byte) []byte
- func AppendTime(dst []byte, t time.Time) ([]byte, error)
- func AppendUUIDBytes(dst []byte, u [16]byte) []byte
- func AppendUint64(dst []byte, v uint64) []byte
- func Intern(b []byte) string
- func IsEightDigits(v uint64) bool
- func ParseDecimalParts(b []byte) (coeff int64, exp int32, err error)
- func ParseEightDigits(v uint64) uint64
- func ReadRaw(l *Lexer, r *Raw)
- func Valid(data []byte) error
- type Appender
- type Lexer
- func (l *Lexer) AddError(err error)
- func (l *Lexer) Data() []byte
- func (l *Lexer) DataAndPos() ([]byte, int)
- func (l *Lexer) Delim(c byte)
- func (l *Lexer) EnterArray() bool
- func (l *Lexer) EnterObject() bool
- func (l *Lexer) Err() error
- func (l *Lexer) Fail(format string, args ...any)
- func (l *Lexer) Finish()
- func (l *Lexer) IsDelim(c byte) bool
- func (l *Lexer) IsNull() bool
- func (l *Lexer) NextElem() bool
- func (l *Lexer) NextMember() bool
- func (l *Lexer) OrderedBail(pos int)
- func (l *Lexer) Pos() int
- func (l *Lexer) RawValue() []byte
- func (l *Lexer) ReadBool() bool
- func (l *Lexer) ReadBytesBase64() []byte
- func (l *Lexer) ReadEnumBytes() []byte
- func (l *Lexer) ReadFloat64() float64
- func (l *Lexer) ReadInt() int
- func (l *Lexer) ReadInt8() int8
- func (l *Lexer) ReadInt16() int16
- func (l *Lexer) ReadInt32() int32
- func (l *Lexer) ReadInt64() int64
- func (l *Lexer) ReadInterface() any
- func (l *Lexer) ReadInternedString() string
- func (l *Lexer) ReadKey() []byte
- func (l *Lexer) ReadNumericBytes() []byte
- func (l *Lexer) ReadQuotedInt() int
- func (l *Lexer) ReadQuotedInt8() int8
- func (l *Lexer) ReadQuotedInt16() int16
- func (l *Lexer) ReadQuotedInt32() int32
- func (l *Lexer) ReadQuotedInt64() int64
- func (l *Lexer) ReadQuotedUint() uint
- func (l *Lexer) ReadQuotedUint8() uint8
- func (l *Lexer) ReadQuotedUint16() uint16
- func (l *Lexer) ReadQuotedUint32() uint32
- func (l *Lexer) ReadQuotedUint64() uint64
- func (l *Lexer) ReadString() string
- func (l *Lexer) ReadTextBytes() []byte
- func (l *Lexer) ReadTime() time.Time
- func (l *Lexer) ReadUUIDBytes() [16]byte
- func (l *Lexer) ReadUint() uint
- func (l *Lexer) ReadUint8() uint8
- func (l *Lexer) ReadUint16() uint16
- func (l *Lexer) ReadUint32() uint32
- func (l *Lexer) ReadUint64() uint64
- func (l *Lexer) SetPos(pos int)
- func (l *Lexer) Skip()
- func (l *Lexer) SkipObject()
- func (l *Lexer) WantComma()
- type QuotedInt64Marker
- type Raw
- type Reader
- type SyntaxError
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrDecimalRange = errors.New("zerojson: decimal out of range")
ErrDecimalRange reports that ParseDecimalParts was given a decimal whose coefficient overflows int64 or whose exponent exceeds maxDecimalExponent.
var ErrMalformedDecimal = errors.New("zerojson: malformed decimal")
ErrMalformedDecimal reports that ParseDecimalParts was given text that is not a plain decimal number (optional '-', digits, optional '.', digits).
Functions ¶
func AppendBool ¶
AppendBool appends the JSON boolean literal.
func AppendBytesBase64 ¶
AppendBytesBase64 appends a byte slice as a JSON value, matching encoding/json and easyjson: a nil slice appends the literal null, and any non-nil slice (including an empty one, which appends "") appends its standard-base64 encoding as a quoted string. This is the encoding both oracles use for a []byte (or named byte-slice) field/element/value; the element type is uint8 and does not carry its own Marshaler.
func AppendDecimalParts ¶
AppendDecimalParts appends coeff * 10^exp in plain decimal notation — dependency-free, no big.Int, no external decimal library. The coefficient's scale is preserved: AppendDecimalParts(dst, 18550, -2) appends "185.50", and AppendDecimalParts(dst, 0, -2) appends "0.00". This is an infallible append-style leaf helper (like every other zerojson Append* function): exp outside [-maxDecimalExponent, maxDecimalExponent] panics, since producing it would mean a caller bug (an out-of-range value being encoded), not bad input.
func AppendFloat32 ¶
AppendFloat32 appends f formatted exactly as easyjson's jwriter.Writer.Float32 does (strconv.AppendFloat with the value widened to float64 but the 32-bit shortest-round-trip precision request). See AppendFloat64's doc for the format-matching rationale and the NaN/Infinity error contract.
func AppendFloat64 ¶
AppendFloat64 appends f formatted exactly as easyjson's jwriter.Writer.Float64 does: strconv.AppendFloat(dst, f, 'g', -1, 64), with no post-processing. Byte-identity with easyjson is the encode contract; this is a deliberate divergence from encoding/json's float format selection (its manual abs<1e-6||abs>=1e21 'e'-vs-'f' threshold), which disagrees with strconv's raw 'g' format on many values.
NaN and Infinity are not valid JSON number tokens: encoding/json and easyjson both refuse to encode them. AppendFloat64 matches both — dst is returned unchanged with a non-nil error, which the generated AppendZJSON propagates like any other encode error.
func AppendInt64 ¶
AppendInt64 appends v as a bare signed integer.
func AppendInterface ¶
AppendInterface appends an arbitrary value, matching easyjson's handling of interface{} fields (which fall back to encoding/json). Nested maps therefore have sorted keys, unlike the range-order maps zerojson emits for statically-typed map fields.
func AppendQuotedInt64 ¶
AppendQuotedInt64 appends v as a quoted integer, matching the common string-encoded Int64 convention used to survive float64-based decoders.
func AppendQuotedUint64 ¶
AppendQuotedUint64 appends v as a quoted unsigned integer, for json:",string" fields (stdlib's "quoted" convention).
func AppendRaw ¶
AppendRaw appends r to dst verbatim, or the literal null if r is nil (matching json.RawMessage.MarshalJSON). It satisfies the zerojson leaf append contract and is registered as a built-in leaf for Raw.
func AppendString ¶
AppendString appends s as a quoted, escaped JSON string. The scan for escape-needing bytes runs 8 bytes per step; a clean ASCII string reduces to one scan plus one bulk copy.
A byte with the high bit set (>= 0x80) routes to the slow path (appendNonASCII): a valid multi-byte UTF-8 sequence passes through verbatim, an invalid sequence is replaced with replacementEscape. Both match easyjson and encoding/json.
func AppendStringBytes ¶
AppendStringBytes appends b as a quoted, escaped JSON string (for TextMarshaler output).
func AppendTime ¶
AppendTime appends t as a quoted RFC3339 timestamp with nanoseconds, byte-identical to time.Time.MarshalJSON, without the generic layout machinery of time.AppendFormat.
A year outside [0,9999] is not representable in strict RFC 3339; time.Time.MarshalJSON errors and easyjson (which delegates to it) errors identically, so this returns that error rather than emitting a non-RFC3339 string.
func AppendUUIDBytes ¶
AppendUUIDBytes appends u (a raw 16-byte UUID) in canonical quoted form, byte-identical to uuid.UUID.MarshalText, without allocating. Generated code casts a domain UUID type to [16]byte at the call site (e.g. zerojson.AppendUUIDBytes(dst, [16]byte(v.Field))), keeping the runtime itself free of any UUID dependency.
func AppendUint64 ¶
AppendUint64 appends v as a bare unsigned integer.
func Intern ¶
Intern returns a canonical string for b, allocating only the first time a short value is seen while its shard has capacity. Once a shard reaches its fixed cap, new values are copied but not retained. Lookups for retained values are allocation-free.
func IsEightDigits ¶
IsEightDigits reports whether all 8 bytes of v (a little-endian load) are ASCII digits. Exported for custom leaf codecs.
func ParseDecimalParts ¶
ParseDecimalParts parses a plain decimal value (optional leading '-', an integer part, and an optional '.'-prefixed fractional part — no exponent notation) into coeff * 10^exp, preserving fractional scale: "185.50" returns coeff=18550, exp=-2. It never allocates and never panics — malformed input or an out-of-range coefficient/exponent returns ErrMalformedDecimal/ErrDecimalRange, matching the Lexer's contract of reporting errors rather than panicking on untrusted input. Use a decimal library's own parser directly when values may exceed int64 coefficient precision.
func ParseEightDigits ¶
ParseEightDigits converts 8 ASCII digits (little-endian load) to their numeric value in three multiply-accumulate steps. Exported for custom leaf codecs.
func ReadRaw ¶
ReadRaw captures the next JSON value's exact bytes (object, array, string, number, bool, or null — Skip's usual repertoire) into r: a copy, unless the Lexer is in Borrow mode, in which case r aliases the input buffer. It satisfies the zerojson leaf read contract and is registered as a built-in leaf for Raw.
func Valid ¶
Valid reports whether data is syntactically valid JSON: a single top-level value (object, array, string, number, or literal) followed by nothing but optional whitespace, with every byte of every value walked against the full RFC 8259 grammar — unlike the trusted-path Lexer, which is deliberately not a validator (see the package doc and README's composition story: Valid is the separate, opt-in full-payload guarantee for untrusted-boundary call sites; decode itself stays fast and lenient by design for trusted input).
Valid's validity verdict is guaranteed to match encoding/json.Valid's for every input — see conformance's FuzzValidAgainstStdlib — including:
- accepting invalid UTF-8 inside strings, matching v1's byte-oriented scanner, which never decodes UTF-8 (a stricter UTF-8-checking variant, matching encoding/json/v2's default, is a possible future opt-in for a caller that needs to guarantee valid Unicode downstream, not just valid JSON grammar — no current caller needs it);
- lone/unpaired \u-escaped surrogates are grammar-valid (four hex digits is the whole requirement; stdlib does not check surrogate pairing at the grammar level either — only a value-level unescape step, which Valid never performs, replaces one with U+FFFD);
- the same maxNestingDepth of 10000 nested objects/arrays stdlib enforces (matches this package's existing ReadInterface depth cap).
On success (data is valid), Valid performs zero allocations (see TestValidAllocs); on failure it returns a *SyntaxError, which does allocate.
Types ¶
type Appender ¶
Appender is implemented by hand-written types that want to join zerojson's zero-alloc encode path directly, without a -leaf generator registration. The generator detects this method on a field's type the same way it detects MarshalJSON, but with precedence above it: a type offering both AppendZJSON/ReadZJSON and MarshalJSON/UnmarshalJSON gets the fast append/read path.
AppendZJSON must follow the same contract as every other zerojson Append* function: dst is the buffer to append to (never reset or copied), and the result is dst plus the new encoding. On error, the returned buffer holds a partial encoding and must be discarded. This is the exact signature the generator emits for every generated type, so a generated type already satisfies Appender; the generator still dispatches to same-package generated types directly rather than through this interface.
type Lexer ¶
type Lexer struct {
// Borrow makes string reads return views into the input buffer
// instead of copies. Callers must guarantee the input outlives the
// decoded struct.
Borrow bool
// contains filtered or unexported fields
}
Lexer is a minimal pull-parser over a complete JSON document held in memory. Known/declared field values are read by lenient, fast leaf readers (deliberately not full RFC 8259 validation: a bare number may have a leading zero, a string may contain a raw control byte) — the trusted-path design this package is built around. Content the Lexer doesn't otherwise examine is not exempt from grammar checking, though: Skip (unknown fields, Raw passthrough spans) and Finish (trailing data after the top-level value) ARE fully grammar-strict, reusing Valid's own grammar walk (valid.go) — see the README's "Validation" section for the resulting decode-implies-valid composition story, and top-level zerojson.Valid for a standalone full-payload validator.
func (*Lexer) DataAndPos ¶
DataAndPos returns Data() and Pos() together, since generated ordered decoders read both on nearly every candidate key check.
func (*Lexer) EnterArray ¶
EnterArray consumes '[' and reports whether the array has elements; an empty array's ']' is consumed too.
func (*Lexer) EnterObject ¶
EnterObject consumes '{' and reports whether the object has members; an empty object's '}' is consumed too. Malformed input records an error and returns false.
func (*Lexer) Fail ¶
Fail records a decode error with source-offset context, for custom leaf read functions.
func (*Lexer) Finish ¶
func (l *Lexer) Finish()
Finish verifies nothing but optional whitespace remains at the Lexer's current position, and records an error otherwise — "invalid character %q after top-level value", the same condition (and message) as encoding/json.Unmarshal's trailing-garbage rejection, and matching easyjson's jlexer.Lexer.Consumed. Generated top-level decode entry points (UnmarshalZJSON(Borrow), a pooled Decoder's Decode(Borrow)) call this exactly once, after the top-level value has been decoded; it must never be called from a nested/recursive decode, which has no business opining on what comes after the value it was asked to parse.
func (*Lexer) IsNull ¶
IsNull consumes a null literal if present and reports whether it did. It is called before every field dispatch, so the non-null case exits on the first byte.
func (*Lexer) NextElem ¶
NextElem consumes the ',' between array elements (returning true) or the closing ']' (returning false).
func (*Lexer) NextMember ¶
NextMember consumes the ',' between object members (returning true) or the closing '}' (returning false). Unlike the WantComma/IsDelim pair it replaces, it costs a single whitespace skip per member.
func (*Lexer) OrderedBail ¶
OrderedBail rewinds the lexer to pos (the position where a generated ordered decoder began a speculative attempt) and clears any error recorded since, so the caller can retry with the fallback keyed decoder with zero observable trace of the abandoned attempt.
func (*Lexer) RawValue ¶
RawValue returns the raw bytes of the next value (quotes included for strings), for delegation to a type's own UnmarshalJSON.
func (*Lexer) ReadBytesBase64 ¶
ReadBytesBase64 reads either the usual base64 JSON string or encoding/json's additional array-of-byte-values input form into a fresh byte slice. The string path is the common fast path. null is handled by the caller's IsNull check before this is reached; null elements inside a numeric array decode as zero, matching encoding/json's scalar null behavior.
func (*Lexer) ReadEnumBytes ¶
ReadEnumBytes returns the raw bytes of a string value for switch dispatch against known enum constants. The returned slice usually aliases the input buffer and must not be retained; assign a constant or copy. Escaped values (possible for unknown enum values containing the HTML set, which the encoder escapes) take an allocating unescape path — declared enum constants never need it.
func (*Lexer) ReadFloat64 ¶
ReadFloat64 parses a quoted-or-bare floating point number.
func (*Lexer) ReadInt ¶
ReadInt/ReadUint parse a quoted-or-bare integer range-checked against the build platform's `int`/`uint` width, so generated code for an `int` or `uint` field never blind-casts a 64-bit read into a narrower word. The bounds are the untyped constants math.MaxInt/math.MinInt/math.MaxUint: on a 64-bit target they equal the int64/uint64 limits, so these are the exact same reads as ReadInt64/ReadUint64 (the width check the callee already performs is the whole check — zero added cost); on a 32-bit target they tighten to the 32-bit range, turning what would otherwise be a silent wrap into a decode error, matching encoding/json.
func (*Lexer) ReadInt8 ¶
ReadInt8/16/32/64 parse a quoted-or-bare signed integer, range-checked against the named width: a value outside [MinIntN, MaxIntN] is a decode error, not a silently wrapped cast (see readSigned).
func (*Lexer) ReadInterface ¶
ReadInterface decodes an arbitrary JSON value into the same shapes easyjson's jlexer.Interface produces: map[string]interface{}, []interface{}, string, float64, bool, nil. Object keys are copied because this generic surface accepts arbitrary, potentially high-cardinality keys; string values honor the Borrow flag.
func (*Lexer) ReadInternedString ¶
ReadInternedString returns the string value from the bounded process-wide intern table. Retained short values (identifiers, currency codes, and other low-cardinality strings) reuse one allocation; values that arrive after a shard reaches its cap are copied without being retained.
func (*Lexer) ReadKey ¶
ReadKey returns the next object key, usually as a view into the input. Escaped keys (rare: only keys containing quotes, control chars, or the HTML set '<' '>' '&') take an allocating unescape path.
Unlike a known/declared field's VALUE — which stays leniently parsed by the trusted-path leaf readers (a bare number may have a leading zero, a string a raw control byte or invalid escape) — an object KEY is grammar- strict: a raw control byte or an invalid escape sequence in a key is rejected, matching json.Valid / encoding/json. Keys are validated on every path that reaches ReadKey (the general keyed fallback, map decode, and the ordered decoder's skip of an unknown key); the ordered fast path never reaches here for a declared key — it matches the exact declared- name literal bytes, so an escaped or control-bearing key can never satisfy that compare and always falls through to this strict reader. See README's validation composition story.
func (*Lexer) ReadNumericBytes ¶
ReadNumericBytes returns the raw bytes of the next number token (quoted or bare), for custom leaf-type read functions to parse themselves.
func (*Lexer) ReadQuotedInt ¶
func (*Lexer) ReadQuotedInt8 ¶
func (*Lexer) ReadQuotedInt16 ¶
func (*Lexer) ReadQuotedInt32 ¶
func (*Lexer) ReadQuotedInt64 ¶
func (*Lexer) ReadQuotedUint ¶
func (*Lexer) ReadQuotedUint8 ¶
func (*Lexer) ReadQuotedUint16 ¶
func (*Lexer) ReadQuotedUint32 ¶
func (*Lexer) ReadQuotedUint64 ¶
func (*Lexer) ReadString ¶
ReadString returns the string value, copying unless Borrow is set.
func (*Lexer) ReadTextBytes ¶
ReadTextBytes returns the content of a string value for delegation to UnmarshalText. The slice may alias the input buffer; UnmarshalText implementations must copy what they keep (they do, per its contract).
func (*Lexer) ReadTime ¶
ReadTime parses a quoted RFC3339 timestamp. UTC ("Z") timestamps — the only kind zerojson emits — parse at fixed digit positions; offsets and edge cases fall back to time.Parse.
func (*Lexer) ReadUUIDBytes ¶
ReadUUIDBytes parses a quoted UUID into its raw 16-byte form. The canonical 36-char form is decoded at fixed offsets with a hex table — no scan, no external UUID library; anything else (uppercase variants also take the fast path; urn:/braced/un-hyphenated forms, escapes) falls back to parseUUIDBytes. Generated code casts the result to a domain UUID type (e.g. uuid.UUID(l.ReadUUIDBytes())) in the caller's own package, which keeps the zerojson runtime itself free of any UUID dependency.
func (*Lexer) ReadUint8 ¶
ReadUint8/16/32/64 parse a quoted-or-bare unsigned integer, range-checked against the named width (see readUnsigned).
func (*Lexer) ReadUint16 ¶
func (*Lexer) ReadUint32 ¶
func (*Lexer) ReadUint64 ¶
func (*Lexer) SetPos ¶
SetPos sets the current byte offset. Generated ordered decoders use it to advance past a matched literal directly — compact JSON (what zerojson's own encoder emits) never has whitespace to skip there.
func (*Lexer) Skip ¶
func (l *Lexer) Skip()
Skip discards the next value (used for unknown keys and, via RawValue, captured Raw spans). Unlike the known-field decode primitives, Skip grammar-checks what it discards, by delegating to Valid's own grammar walk (validValue, valid.go) rather than duplicating it: numbers must satisfy RFC 8259's number grammar, literals must match exactly, strings must have valid escape sequences and no raw unescaped control bytes, and nested object/array content is fully walked (recursively, to the same maxDepth cap Valid enforces) rather than merely brace/bracket-counted.
This is what makes a successful decode imply the whole payload was grammar-valid JSON (see README's composition story): every byte is either consumed by a known field's own decode statement or grammar-checked here — the two together, plus Finish's trailing-data check, cover the entire document, at every level of nesting a decode walks (a genuinely unknown field's value, and the ordered decoder's inline skip of one, both go through this same method). The one thing this deliberately does not touch is the known-field decode primitives (readStringBytes, readNumericBytes) — those stay lenient and fast; only content the caller never otherwise inspects gets the stricter treatment.
func (*Lexer) SkipObject ¶
func (l *Lexer) SkipObject()
SkipObject requires the next value to be a JSON object and skips it (grammar-validated, with any members ignored), recording an error if the next value is anything else. This is the decode contract of a struct{} (set-map) value: encoding/json and easyjson both accept an object of any contents (unknown members ignored) or null for such a value, but reject a number, array, or string. null is consumed by the caller's IsNull check before this is reached, so only the object-or-error decision remains here.
type QuotedInt64Marker ¶
type QuotedInt64Marker interface {
ZeroJSONQuotedInt64()
}
QuotedInt64Marker is an explicit promise used by zerojsongen for the common int64-as-quoted-base-10 wire convention. A named int64 type that implements MarshalJSON/UnmarshalJSON and this marker gets the direct no-allocation integer path; without the marker its JSON methods are delegated normally. The marker must be implemented on a value receiver so map values carry the same promise even though they are not addressable.
type Raw ¶
type Raw []byte
Raw holds an unprocessed JSON value: it decodes by capturing the exact byte span of the next value and encodes by appending those bytes back verbatim — the caller guarantees they are valid JSON, matching encoding/json.RawMessage's contract exactly (including its "nil encodes as null, everything else is appended as-is, even if empty or invalid" behavior).
This enables passthrough/projection patterns: a struct that declares only the fields a reader actually needs, routing the rest through unexamined —
type Route struct {
Table string `json:"table"`
Set zerojson.Raw `json:"set"`
}
decodes only Table and Set (the rest of the wire object is skipped by the ordered decoder's inline-skip, or the fallback's default case) without materializing or even parsing the skipped fields, and re-encodes Set byte-for-byte unchanged — the passthrough/routing case this was built for.
In borrow-mode decodes the bytes alias the input buffer (same rule as borrowed strings — the caller must guarantee the input outlives the decoded struct); in copy-mode decodes they're copied.
type Reader ¶
type Reader interface {
ReadZJSON(l *Lexer)
}
Reader is Appender's decode counterpart. It must be implemented on a pointer receiver (it mutates the receiver) and must report errors the same way every other leaf Read function does — via the Lexer's Fail or AddError, not a return value — so the generator can emit it as a plain decode statement instead of an error-checked one.
type SyntaxError ¶
type SyntaxError struct {
Offset int64
// contains filtered or unexported fields
}
SyntaxError reports the first RFC 8259 grammar violation Valid found, shaped like encoding/json's error of the same name (an Offset plus an Error() string). Valid's parity contract with encoding/json.Valid is about the valid/invalid verdict only — Offset and the message text are for humans debugging bad input, not part of that contract.
func (*SyntaxError) Error ¶
func (e *SyntaxError) Error() string