jsonbind

package
v0.5.30 Latest Latest
Warning

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

Go to latest
Published: Sep 8, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

Documentation

Overview

Package jsonbind provides generated, reflection-free JSON document codecs.

Index

Constants

View Source
const DefaultMaxJSONBodyBytes int64 = 1 << 20

DefaultMaxJSONBodyBytes is the default JSON document limit (1 MiB).

View Source
const DefaultMaxNestingDepth = 90

DefaultMaxNestingDepth bounds how deeply objects and arrays may nest unless SetMaxNestingDepth raises it.

The walk is recursive — SkipValue and Any call themselves, and a generated decoder calls the next one down — so without a bound the depth of the document is the depth of the Go stack, and a request body decides it. The bound has to hold on the smallest stack this package runs on, and that is not the host's: TinyGo's goroutine stacks are fixed, and its wasm targets start with 64 KiB, on which SkipValue overflows at about a hundred open brackets. Worse, a wasm overflow is detected only at exit, so the request that caused it gets a wrong answer rather than an error. Ninety is under that with room for the frames around the parser, and nothing legitimate nests anywhere near it; encoding/json's ten thousand was the previous value and is where a host with a growable stack may put it back.

A document deeper than the bound is refused as a parse failure, which generated binders already map to 400.

Variables

View Source
var ErrArrayTooLong = errors.New("jsonbind: too many array elements")

ErrArrayTooLong reports a JSON array carrying more elements than the fixed-length Go array it decodes into can hold.

It is the cause of the Error ParseArray returns, so a caller that wants to tell a too-long array from a malformed one asks errors.Is rather than matching the message.

View Source
var ErrBodyTooLarge = errors.New("jsonbind: JSON body too large")

ErrBodyTooLarge reports that a JSON document exceeded its configured limit.

View Source
var ErrIntegerRange = newError("json_parse", "JSON number out of range", nil)

ErrIntegerRange is returned by generated code when a JSON integer is outside the range of the field's declared width. The generated codec makes the comparison, because the bound is a constant it knows at generation; this is the error it has to name, and exporting one value keeps the check from needing a fmt or errors import inside a generated file.

Functions

func AppendAny added in v0.4.0

func AppendAny(dst []byte, v any) []byte

AppendAny appends an arbitrary Go value produced by rest-field decoding. It covers the shapes Parser.Any yields plus the common scalar types, and any type carrying its own encoder through Appender; anything else is written as null rather than failing an otherwise valid response.

The Appender arm is what keeps a user type out of that null. Before it a value the switch did not name — which is every named struct — reached the default and encoded as null, producing a wrong document rather than a reported error. It sits after the concrete cases so a builtin shape is still matched by identity rather than by method set.

func AppendBase64 added in v0.5.24

func AppendBase64(dst []byte, v []byte) []byte

AppendBase64 appends v as a base64 JSON string.

A nil slice and an empty one both write "", which is the rule this codec already applies to a nil slice and a nil map: nothing on the Go side separates "no bytes" from "an empty blob", so nothing on the wire does either. encoding/json writes null for the nil case.

func AppendBool added in v0.4.0

func AppendBool(dst []byte, v bool) []byte

AppendBool appends a JSON boolean.

func AppendFloat added in v0.4.0

func AppendFloat(dst []byte, v float64) []byte

AppendFloat appends a JSON number using encoding/json's formatting: shortest round-trip, switching to exponent form outside [1e-6, 1e21) and trimming the exponent's leading zero.

func AppendFuncFor added in v0.5.21

func AppendFuncFor[T any]() (func([]byte, T) []byte, bool)

AppendFuncFor returns T's registered append-form encoder. Callers that encode the same T repeatedly — a stream writing events — resolve it once instead of paying a registry lookup per value.

func AppendInt added in v0.4.0

func AppendInt(dst []byte, v int64) []byte

AppendInt appends a JSON number.

func AppendRaw added in v0.4.0

func AppendRaw(dst []byte, raw []byte) []byte

AppendRaw appends an already-encoded JSON value, or null when it is empty.

func AppendString added in v0.4.0

func AppendString(dst []byte, s string) []byte

AppendString appends a JSON string literal. Like encoding/json's encoder it escapes <, > and & so the result is safe to embed in HTML, and U+2028/U+2029 so it stays valid JavaScript.

func AppendUint added in v0.4.0

func AppendUint(dst []byte, v uint64) []byte

AppendUint appends a JSON number.

func DecodeJSON

func DecodeJSON[T any](r io.Reader) (T, error)

DecodeJSON decodes one JSON value from r into T using a generated codec. It does not inspect HTTP headers or use reflection on T's fields.

func DecodeJSONAny added in v0.4.0

func DecodeJSONAny(raw []byte) (any, error)

DecodeJSONAny decodes any JSON value into the Go shapes encoding/json uses for an `any` destination.

func DecodeJSONBool

func DecodeJSONBool(raw []byte) (bool, error)

DecodeJSONBool decodes a JSON boolean.

func DecodeJSONBoolSlice

func DecodeJSONBoolSlice(raw []byte) ([]bool, error)

DecodeJSONBoolSlice decodes a JSON array of booleans.

func DecodeJSONBytes added in v0.5.4

func DecodeJSONBytes[T any](data []byte) (T, error)

DecodeJSONBytes decodes one JSON value already held in memory into T.

The reader entries above exist because an HTTP body arrives as a stream. A caller that already has the whole document — a WebSocket message, say — has nothing to read, and going through a reader would allocate one and copy the document into a second buffer for every call.

The limit belongs to whoever produced the bytes, so none is applied here. A type carrying Decoder is read through it, on the terms EncodeJSON states for the other direction.

func DecodeJSONFloat64

func DecodeJSONFloat64(raw []byte) (float64, error)

DecodeJSONFloat64 decodes a JSON number as float64.

func DecodeJSONFloat64Slice

func DecodeJSONFloat64Slice(raw []byte) ([]float64, error)

DecodeJSONFloat64Slice decodes a JSON array of floats.

func DecodeJSONInt

func DecodeJSONInt(raw []byte) (int, error)

DecodeJSONInt decodes a JSON number as int.

func DecodeJSONInt64

func DecodeJSONInt64(raw []byte) (int64, error)

DecodeJSONInt64 decodes a JSON number as int64.

func DecodeJSONInt64Slice

func DecodeJSONInt64Slice(raw []byte) ([]int64, error)

DecodeJSONInt64Slice decodes a JSON array of int64s.

func DecodeJSONIntSlice

func DecodeJSONIntSlice(raw []byte) ([]int, error)

DecodeJSONIntSlice decodes a JSON array of ints.

func DecodeJSONLimit

func DecodeJSONLimit[T any](r io.Reader, limit int64) (T, error)

DecodeJSONLimit is DecodeJSON with a per-call byte limit. A non-positive limit uses MaxJSONBodyBytes.

func DecodeJSONMapStringBool added in v0.4.0

func DecodeJSONMapStringBool(raw []byte) (map[string]bool, error)

DecodeJSONMapStringBool decodes a JSON object of booleans.

func DecodeJSONMapStringFloat64 added in v0.4.0

func DecodeJSONMapStringFloat64(raw []byte) (map[string]float64, error)

DecodeJSONMapStringFloat64 decodes a JSON object of floats.

func DecodeJSONMapStringInt added in v0.4.0

func DecodeJSONMapStringInt(raw []byte) (map[string]int, error)

DecodeJSONMapStringInt decodes a JSON object of ints.

func DecodeJSONMapStringInt64 added in v0.4.0

func DecodeJSONMapStringInt64(raw []byte) (map[string]int64, error)

DecodeJSONMapStringInt64 decodes a JSON object of int64s.

func DecodeJSONMapStringString

func DecodeJSONMapStringString(raw []byte) (map[string]string, error)

DecodeJSONMapStringString decodes a JSON object of strings.

func DecodeJSONString

func DecodeJSONString(raw []byte) (string, error)

DecodeJSONString decodes a JSON string value.

func DecodeJSONStringSlice

func DecodeJSONStringSlice(raw []byte) ([]string, error)

DecodeJSONStringSlice decodes a JSON array of strings.

func EncodeJSON

func EncodeJSON[T any](w io.Writer, v T) error

EncodeJSON encodes v as compact JSON to w using a generated codec, or, for a type carrying its own, through Appender.

The interface is tried first. A type this run planned and also carries a method is one whose author wrote an encoder, and encoding it through the generated one instead would silently produce bytes they did not intend. This is how encoding/json resolves the same conflict, letting the method win over walking the fields. For a declared codec the two are the same code, since the emitted method delegates to the emitted function.

It does not set HTTP headers or status.

func FieldError

func FieldError(field, message string, cause error) error

FieldError annotates a JSON decoding error with its document field.

func GetBuffer added in v0.4.0

func GetBuffer() *[]byte

GetBuffer borrows an encode buffer. Generated code returns it with PutBuffer once the bytes have been written out.

func IsBlank added in v0.4.0

func IsBlank(data []byte) bool

IsBlank reports whether data holds no JSON document at all. Generated decoders treat that as the zero value rather than a parse error, which is how an absent body and an empty config file have always behaved.

func MaxJSONBodyBytes

func MaxJSONBodyBytes() int64

MaxJSONBodyBytes returns the effective JSON document limit.

func MaxNestingDepth added in v0.5.27

func MaxNestingDepth() int

MaxNestingDepth returns the effective nesting bound.

func ParseBase64 added in v0.5.24

func ParseBase64(p *Parser, field string) ([]byte, error)

ParseBase64 decodes a base64 JSON string member into a new slice. A null member decodes as a nil slice, which leaves an already-bound field alone the way a null array does.

func ParseBase64Into added in v0.5.24

func ParseBase64Into(p *Parser, field string, dst []byte) error

ParseBase64Into decodes a base64 JSON string member into a fixed-length destination, under the same two-ended contract [ParseArray] has: a short payload fills what arrived and zeroes the rest, and one too long to fit is ErrArrayTooLong rather than a blob quietly cut to length.

func PutBuffer added in v0.4.0

func PutBuffer(b *[]byte)

PutBuffer returns an encode buffer to the pool. Oversized buffers are dropped so one large document does not pin memory for the process lifetime.

func RawJSONArray

func RawJSONArray(raw []byte) ([][]byte, error)

RawJSONArray splits a JSON array into its raw elements. Elements alias raw.

func ReadLimitHint added in v0.4.0

func ReadLimitHint(r io.Reader, limit, hint int64) ([]byte, error)

ReadLimitHint reads at most limit bytes from r, with an expected size. A caller that knows the length up front — an HTTP handler with a Content-Length, say — lets the whole body land in one allocation instead of the repeated grow-and-copy io.ReadAll performs. A wrong hint costs nothing but the usual growth.

func RegisterAppend added in v0.5.21

func RegisterAppend[T any](fn func([]byte, T) []byte)

RegisterAppend registers a generated append-form encoder for T: the function a generated writer body is built from, appending one compact JSON value with no trailing newline.

The writer form above frames and writes a whole document, which is right for a response body but wrong for a caller composing a larger frame — an SSE event, a JSON array element — who would pay a second buffer and copy to unwrap it. This form hands the bytes over where they are wanted instead.

func RegisterDecode

func RegisterDecode[T any](fn func([]byte) (T, error))

RegisterDecode registers a generated JSON document decoder for T.

func RegisterEncode

func RegisterEncode[T any](fn func(io.Writer, T) error)

RegisterEncode registers a generated compact JSON encoder for T.

func SetMaxJSONBodyBytes

func SetMaxJSONBodyBytes(n int64)

SetMaxJSONBodyBytes changes the process-wide JSON document limit.

func SetMaxNestingDepth added in v0.5.28

func SetMaxNestingDepth(n int)

SetMaxNestingDepth changes the process-wide nesting bound. Zero or a negative value restores DefaultMaxNestingDepth. A host running on a growable stack can raise it; a TinyGo target should raise it only together with -stack-size.

func SortedKeys added in v0.4.0

func SortedKeys[V any](m map[string]V) []string

SortedKeys orders map keys so a map encodes deterministically, matching encoding/json's behaviour. Generated encoders call it for every map-typed field and for payload:"*" rest maps.

Types

type Appender added in v0.5.10

type Appender interface {
	AppendJSONTo(dst []byte) []byte
}

Appender is a type that encodes itself as JSON by appending to dst and returning the extended slice.

This is the method form of what the generator already emits, so a generated codec satisfies it by delegation and a hand-written one costs the same as the generated body would have.

There is no error result. The append path has none anywhere below this point, and every value that reaches it is one the caller already holds, so an implementation that cannot produce a document for its own value has no state worth reporting. An implementation must append valid JSON for every value of its type.

type Declaration added in v0.5.10

type Declaration struct{}

Declaration is what an annotation below returns. It carries nothing: the value exists only so the annotation can be written as a package-level declaration, which is where generation reads it.

func GenerateCodec added in v0.5.10

func GenerateCodec[T any]() Declaration

GenerateCodec asks for T's encoder and decoder, and for both methods.

func GenerateDecoder added in v0.5.10

func GenerateDecoder[T any]() Declaration

GenerateDecoder asks for T's decoder and for Decoder alone. Use it for a type only ever read.

func GenerateEncoder added in v0.5.10

func GenerateEncoder[T any]() Declaration

GenerateEncoder asks for T's encoder and for Appender alone. Use it for a type only ever written, so a decoder is not carried into the binary with it.

type Decoder added in v0.5.10

type Decoder interface {
	DecodeJSONFrom(data []byte) error
}

Decoder is a type that decodes one complete JSON document into itself.

data holds exactly one JSON value. The implementation fills the receiver, so the method belongs on the pointer and *T rather than T is what satisfies this.

Unlike Appender, this does not compose to any depth for free: a nested field is decoded by walking a Parser, and a method taking a complete slice joins that walk only by being handed the sub-document, which costs scanning that region twice. Generated decoders take that path for a field whose type satisfies this and no other field, so a document holding none pays nothing.

type Error

type Error struct {
	Code    string
	Message string
	Field   string
	// contains filtered or unexported fields
}

Error describes a transport-neutral JSON mapping failure.

func AsError

func AsError(err error) (*Error, bool)

AsError finds a JSON Error without using reflection-dependent errors.As.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Unwrap

func (e *Error) Unwrap() error

type Parser added in v0.4.0

type Parser struct {
	// contains filtered or unexported fields
}

Parser reads a JSON document in a single forward pass. Generated codecs drive it directly: values are parsed out of the input buffer in place, so decoding allocates only the strings, slices and maps that end up in the result.

It does not use reflect and does not import encoding/json.

func NewParser added in v0.4.0

func NewParser(data []byte) *Parser

NewParser returns a Parser reading data. data is not copied, and values returned by RawValue alias it.

func (*Parser) Any added in v0.4.0

func (p *Parser) Any() (any, error)

Any decodes an arbitrary JSON value into the same Go shapes encoding/json produces for an `any` destination.

func (*Parser) ArrayNext added in v0.4.0

func (p *Parser) ArrayNext(n int) (bool, error)

ArrayNext reports whether another element follows. n is the element index.

func (*Parser) ArrayStart added in v0.4.0

func (p *Parser) ArrayStart() (isNull bool, err error)

ArrayStart consumes '['. A JSON null reports isNull and consumes the literal.

func (*Parser) Bool added in v0.4.0

func (p *Parser) Bool() (bool, error)

Bool decodes a JSON boolean. null decodes as false.

func (*Parser) End added in v0.4.0

func (p *Parser) End() error

End reports an error when anything but whitespace follows the document.

func (*Parser) Float64 added in v0.4.0

func (p *Parser) Float64() (float64, error)

Float64 decodes a JSON number. null decodes as 0.

func (*Parser) Int added in v0.4.0

func (p *Parser) Int() (int, error)

Int decodes a JSON number as int. null decodes as 0.

func (*Parser) Int64 added in v0.4.0

func (p *Parser) Int64() (int64, error)

Int64 decodes a JSON number as int64. null decodes as 0.

func (*Parser) IsNull added in v0.4.0

func (p *Parser) IsNull() bool

IsNull consumes a null literal when the next value is null.

func (*Parser) ObjectKey added in v0.4.0

func (p *Parser) ObjectKey(n int) (key []byte, ok bool, err error)

ObjectKey returns the next member name, or ok=false at '}'. n is the zero-based member index and tells the parser whether a comma is required, so the parser needs no nesting stack of its own.

The returned key aliases parser scratch space and is only valid until the next ObjectKey call.

func (*Parser) ObjectStart added in v0.4.0

func (p *Parser) ObjectStart() (isNull bool, err error)

ObjectStart consumes '{'. A JSON null reports isNull and consumes the literal.

func (*Parser) ParseArray added in v0.5.28

func (p *Parser) ParseArray[T any](field, message string, dst []T, read func(*Parser) (T, error)) error

ParseArray decodes a JSON array field into a fixed-length destination, which the caller passes as a slice over its array: p.ParseArray("cells", msg, out.Cells[:], read).

The two ends of a fixed length are not symmetric. A short array fills what arrived and leaves the rest at the zero value, because a length the Go type states is not a length the document has to restate. A long one is an error: storing the first len(dst) elements would drop the tail, and a decoder that silently loses data is the failure a declared length exists to prevent.

The tail is zeroed rather than left alone, so a member that arrives twice decodes to the second array rather than to the two overlaid.

A JSON null leaves the destination untouched, as [ParseSlice] does. Errors are annotated the same way as ParseSlice; a too-long array reports ErrArrayTooLong as its cause.

func (*Parser) ParseMap added in v0.5.28

func (p *Parser) ParseMap[T any](field, message string, read func(*Parser) (T, error)) (map[string]T, error)

ParseMap decodes a JSON object field, reading each member value with read. A JSON null decodes as a nil map and an empty object as a non-nil empty one. Errors are annotated the same way as ParseSlice.

func (*Parser) ParseSlice added in v0.5.28

func (p *Parser) ParseSlice[T any](field, message string, read func(*Parser) (T, error)) ([]T, error)

ParseSlice decodes a JSON array field, reading each element with read. A JSON null decodes as a nil slice and an empty array as a non-nil empty one. Structural errors are annotated with the field's document name; an element error is annotated with message, or passed through unchanged when message is empty so a nested decoder can report its own fields.

The element type is the method's own type parameter, which is what kept this and its siblings package functions before Go 1.27; it is inferred from read.

func (*Parser) RawValue added in v0.4.0

func (p *Parser) RawValue() ([]byte, error)

RawValue returns the next value's bytes as a subslice of the input. The result aliases the parser's buffer and must be copied to outlive it.

func (*Parser) Reset added in v0.4.0

func (p *Parser) Reset(data []byte)

Reset points p at data, reusing its scratch buffers.

func (*Parser) SkipValue added in v0.4.0

func (p *Parser) SkipValue() error

SkipValue advances past the next value, whatever its shape. Structure is validated; the value's contents are not interpreted.

func (*Parser) String added in v0.4.0

func (p *Parser) String() (string, error)

String decodes a JSON string. null decodes as "".

func (*Parser) Uint64 added in v0.5.23

func (p *Parser) Uint64() (uint64, error)

Uint64 decodes a JSON number as uint64. null decodes as 0, and a negative number is an error rather than a wrapped value.

This is the one unsigned reader. The narrower unsigned widths are read through it and range-checked by the generated codec against bounds it knows at generation, which keeps eight more methods out of the runtime a TinyGo target links.

Jump to

Keyboard shortcuts

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