jsondec

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: May 11, 2026 License: MIT Imports: 14 Imported by: 0

README

jsondec

jsondec is a Go JSON decoding library built around a registration pattern: you compile a decoder for a struct type once and reuse it. Beyond performance, it adds field-level semantics that encoding/json does not have — required fields, presence detection, null vs absent distinction, forbidden fields, structural size limits, and union field types — without requiring custom UnmarshalJSON implementations.


Why not encoding/json

encoding/json has five gaps that matter in production API servers:

No required fields. If a required field is missing from the JSON, encoding/json silently leaves it at its zero value. You cannot distinguish {"count": 0} from {}.

No presence detection. There is no way to know whether a field was sent at all. This makes PATCH endpoints impossible to implement correctly without wrapper types or sentinel values.

No null vs absent distinction. {"ptr": null} and {} both result in a nil pointer. Distinguishing them requires custom unmarshaling on every type that needs it.

No structural limits. You cannot reject a payload that is too large, too deeply nested, or has too many fields. This is a security concern when decoding untrusted input.

No forbidden fields. You cannot declare that a particular key must never appear, which matters for deprecated or privileged fields.

[]byte decodes from base64. In encoding/json, a []byte field expects a base64-encoded JSON string — "aGVsbG8=" becomes []byte("hello"). In jsondec, []byte stores the raw string bytes — "aGVsbG8=" becomes []byte("aGVsbG8="). This is a silent data corruption risk when migrating. If you need base64 decoding, use a string field and decode it explicitly.

jsondec solves all of the above with struct tags and typed field declarations, and adds union field types for APIs that return either a string or an array, or either a string or an embedded object.


Benchmarks

Benchmarks run on Linux/amd64, Intel Xeon Platinum 8370C @ 2.80GHz, 20 runs each, using benchstat.

The jsondec default configuration has ReuseInputBuffer=false, which means it explicitly makes a copy of the input.

Default configuration vs other decoders

Each cell: ns/op · B/op · allocs/op. All lower is better.

Shape jsondec encoding/json encoding/json/v2 goccy/go-json
Small flat object 520 ns · 288 B · 3 allocs 1357 ns · 64 B · 1 alloc 1035 ns · 64 B · 1 alloc 435 ns · 160 B · 2 allocs
Nested catalog 3943 ns · 1224 B · 9 allocs 7984 ns · 1032 B · 19 allocs 6715 ns · 1032 B · 19 allocs 3057 ns · 1488 B · 21 allocs
Numeric telemetry 3310 ns · 784 B · 5 allocs 7321 ns · 832 B · 12 allocs 6299 ns · 832 B · 12 allocs 3520 ns · 976 B · 44 allocs
OpenAI request (escaped) 43064 ns · 17368 B · 71 allocs 53632 ns · 17690 B · 72 allocs 45020 ns · 17690 B · 72 allocs 31374 ns · 17879 B · 105 allocs
OpenAI request (no escape) 33513 ns · 17368 B · 71 allocs 43872 ns · 14106 B · 70 allocs 36625 ns · 14107 B · 70 allocs 26956 ns · 17879 B · 105 allocs

Install

go get github.com/your-org/jsondec

Usage

Declare a decoder once at package level. Call it anywhere.

type User struct {
    ID   int    `json:"id,required"`
    Name string `json:"name"`
    Tags []int  `json:"tags"`
}

var DecodeUser = jsondec.RegisterDecoder[User]()

var u User
err := DecodeUser(data, &u)

RegisterDecoder[T]() compiles type information for T at startup and returns a DecodeFunc[T] — a plain function, concurrent-safe, that you own and can name, test, and pass around. Every call to it after that pays no reflection cost.

If you need options applied to every decode call, use RegisterDecoderOptions:

var DecodeUser = jsondec.RegisterDecoderOptions[User](jsondec.DecoderOptions{
    DisallowUnknownFields: true,
    MaxBytes:              1 << 16,
})

Both functions panic with CompileError at startup if T contains an unsupported type or a duplicate JSON field name. This is intentional — misconfiguration is caught before the program serves traffic.


Performance

Benchmarks were run on Linux, amd64, Intel Xeon Platinum 8370C @ 2.80GHz, GOEXPERIMENT=jsonv2, 20 runs per configuration via benchstat. Full raw output and benchstat summary are in docs/.

Benchmark shapes:

  • Small flat object — a single struct with a handful of primitive fields
  • Nested catalog — structs with nested struct fields
  • Numeric telemetry — a struct with many numeric fields and few strings
  • OpenAI API request (escaped) — a real-world API payload where content fields contain \n escape sequences
  • OpenAI API request (no escape) — the same payload with literal newlines instead of escape sequences

The jsondec results shown below use ReuseInputBuffer=false (the default), which copies the input once before decoding so that decoded strings and byte slices remain valid after the caller releases the buffer.

Time (ns/op, lower is better)
JSON shape jsondec encoding/json encoding/json/v2 goccy/go-json
Small flat object 520 1357 1035 435
Nested catalog 3943 7984 6715 3057
Numeric telemetry 3310 7321 6299 3520
OpenAI API request (escaped) 43064 53632 45020 31374
OpenAI API request (no escape) 33513 43872 36625 26956
Allocated bytes (B/op, lower is better)
JSON shape jsondec encoding/json encoding/json/v2 goccy/go-json
Small flat object 288 64 64 160
Nested catalog 1224 1032 1032 1488
Numeric telemetry 784 832 832 976
OpenAI API request (escaped) 17368 17690 17690 17879
OpenAI API request (no escape) 17368 14106 14107 17879
Allocations (allocs/op, lower is better)
JSON shape jsondec encoding/json encoding/json/v2 goccy/go-json
Small flat object 3 1 1 2
Nested catalog 9 19 19 21
Numeric telemetry 5 12 12 44
OpenAI API request (escaped) 71 72 72 105
OpenAI API request (no escape) 71 70 70 105

What these numbers mean:

On CPU time, jsondec is 2–2.3x faster than encoding/json and 1.6–1.9x faster than encoding/json/v2 across most workloads. goccy/go-json is faster on string-heavy payloads where escape processing dominates; jsondec is faster on numeric-heavy ones.

On allocations, jsondec reduces heap pressure significantly versus both standard library options. The numeric telemetry shape makes this most visible: jsondec makes 5 allocations where goccy makes 44, and encoding/json makes 12.

jsondec allocates more bytes than encoding/json on some shapes because of the input copy that ReuseInputBuffer=false makes. The small flat object goes from 64 B to 288 B. This tradeoff is intentional — the copy is what makes it safe to decode into a struct and then release the original buffer. ReuseInputBuffer=true eliminates this cost when you can manage the buffer lifetime yourself.

With ReuseInputBuffer

When the input buffer is prepared outside the timed section — the realistic pattern for a server that owns the input buffer lifecycle and can manage its lifetime — jsondec with ReuseInputBuffer=true reaches:

JSON shape jsondec (ReuseInputBuffer) encoding/json goccy/go-json
Small flat object 481 ns, 192 B, 2 allocs 1357 ns, 64 B, 1 alloc 435 ns, 160 B, 2 allocs
Nested catalog 4003 ns, 648 B, 8 allocs 7984 ns, 1032 B, 19 allocs 3057 ns, 1488 B, 21 allocs
Numeric telemetry 3223 ns, 528 B, 4 allocs 7321 ns, 832 B, 12 allocs 3520 ns, 976 B, 44 allocs
OpenAI API request (escaped) 41732 ns, 10584 B, 70 allocs 53632 ns, 17690 B, 72 allocs 31374 ns, 17879 B, 105 allocs
OpenAI API request (no escape) 29593 ns, 10584 B, 70 allocs 43872 ns, 14106 B, 70 allocs 26956 ns, 17879 B, 105 allocs

The no-escape OpenAI result uses the shared raw variant, where the same unescaped input buffer is decoded repeatedly without restoration between calls. This is the ceiling of what jsondec can achieve on a large string-heavy payload: 29.6µs versus encoding/json's 43.9µs and goccy's 27.0µs.


Struct tags

jsondec reads the json struct tag. The full syntax is:

`json:"<name>,<options>"`

Where <name> is the JSON key to read from (empty means use the Go field name), and the only option jsondec recognises is required. Everything else (omitempty, etc.) is ignored — jsondec is decode-only.

type Order struct {
    // Required fields — decoding fails if these keys are absent.
    ID         int    `json:"id,required"`
    CustomerID int    `json:"customer_id,required"`

    // Optional fields — zero value if absent, no error.
    Status string `json:"status"`
    Notes  string `json:"notes"`

    // Renamed field — reads from "ts", not "CreatedAt".
    CreatedAt time.Time `json:"ts"`

    // Skipped field — never populated, even if the key is present.
    InternalCache string `json:"-"`

    // Forbidden field — decoding fails if this key appears.
    // See the Forbidden type in the Field Types section.
    AdminOverride jsondec.Forbidden `json:"__admin"`
}

A struct can have up to 64 required fields. Fields without a json tag use the Go field name as the key.


Field types

These types are used as struct field types. They are how you express field-level semantics beyond "decode this value or leave it at zero."

Presence and nullability

These four types cover every combination of "was this field in the JSON" and "was it null." The right one to use depends entirely on which of those questions your application needs to answer.

Consider a PATCH endpoint for a user profile. The client sends only what it wants to change. Three different fields have three different requirements:

type UserPatch struct {
    // Name can be updated but not cleared — null is not meaningful here.
    // Absent: don't touch. Present with value: update. Present with null: error.
    Name jsondec.Optional[string] `json:"name"`

    // Bio can be updated or explicitly cleared by sending null.
    // The field is always present in responses, but may be null.
    // Absent: leave as-is (zero value, Null=false). Null: clear it. Value: update.
    Bio jsondec.Nullable[string] `json:"bio"`

    // Email supports full PATCH semantics: absent, null, and value are all distinct.
    // Absent: don't touch. Null: clear the email address. Value: update it.
    Email jsondec.OptionalNullable[string] `json:"email"`

    // AdminNote: we want to know if the caller sent this key at all,
    // but we store the value elsewhere and don't need it decoded here.
    AdminNote jsondec.Present `json:"admin_note"`
}

After decoding:

// Name
if patch.Name.Present {
    db.SetName(patch.Name.Value)
}

// Bio
if patch.Bio.Null {
    db.ClearBio()
} else if patch.Bio.Value != "" {
    db.SetBio(patch.Bio.Value)
}

// Email
if patch.Email.Present {
    if patch.Email.Null {
        db.ClearEmail()
    } else {
        db.SetEmail(patch.Email.Value)
    }
}

// AdminNote
if patch.AdminNote.Present {
    audit.Log("caller included admin_note key, null=%v", patch.AdminNote.Null)
}

Optional[T any]

type Optional[T any] struct {
    Present bool
    Value   T
}

Present is false when the field was absent; true when it appeared with a value. Explicit JSON null is accepted only if T itself accepts null — Optional[*string] accepts null (sets Value to nil pointer), Optional[string] does not.

Nullable[T any]

type Nullable[T any] struct {
    Null  bool
    Value T
}

Null is true when the JSON value was null. Does not distinguish absent from null — a missing field leaves Null=false and Value at its zero value, identical to a present field with the zero value. Use OptionalNullable if you need that distinction.

OptionalNullable[T any]

type OptionalNullable[T any] struct {
    Present bool
    Null    bool
    Value   T
}

Covers all three states: absent (Present=false), null (Present=true, Null=true), value (Present=true, Null=false, Value=decoded).

Present

type Present struct {
    Present bool
    Null    bool
}

Records presence and whether the value was null, without storing the value itself. The JSON value is validated and skipped.


Raw preservation

These types store a validated JSON value without decoding it into Go types. The bytes they store are a slice of the original input, preserving whitespace exactly as it appeared.

Type Accepted JSON Rejects
RawValue Any valid value Nothing valid
RawObject Object or null Arrays, strings, numbers, booleans
RawArray Array or null Objects, strings, numbers, booleans
RawUnion Any valid value Nothing valid — also records the kind
type Config struct {
    // Schema can be any JSON object — store it raw for later validation.
    Schema jsondec.RawObject `json:"schema"`

    // Tags can be any JSON array — store raw to decode later with a specific decoder.
    Tags jsondec.RawArray `json:"tags"`

    // Value is a union field — could be a string, number, or object depending on type.
    Value jsondec.RawUnion `json:"value"`
}

// After decoding, branch on kind:
switch config.Value.Kind {
case jsondec.KindString:
    // decode config.Value.Bytes as a string
case jsondec.KindObject:
    // decode config.Value.Bytes as a specific struct
case jsondec.KindNumber:
    // decode config.Value.Bytes as a number
}

RawValue

type RawValue struct {
    Bytes   []byte
    Present bool
}

RawObject

type RawObject struct {
    Bytes   []byte
    Present bool
}

RawArray

type RawArray struct {
    Bytes   []byte
    Present bool
}

RawUnion

type RawUnion struct {
    Kind  JSONKind
    Bytes []byte
}

Kind is one of: KindNull, KindObject, KindArray, KindString, KindNumber, KindBool, KindInvalid.


Union fields

jsondec has three ways to handle a JSON field that can be more than one type:

RawUnion — covered above under raw preservation — accepts any JSON type and records which kind it was, so you can branch after decoding. It is the general-purpose union mechanism and imposes no constraints on what types are valid.

StringOrSlice and StringOrObject are the two higher-level union types. They decode directly into Go values rather than raw bytes, for the specific and common cases where an API returns either a string or a collection for the same key.

StringOrSlice[T any]

Decodes a JSON string or a JSON array of T. When the value is a string, IsString is true and String holds it. When it is an array, IsString is false and Slice holds the decoded elements.

type StringOrSlice[T any] struct {
    IsString bool
    String   string
    Slice    []T
}

// Handles both:
//   "tags": "featured"
//   "tags": ["featured", "new", "sale"]
type Product struct {
    Tags jsondec.StringOrSlice[string] `json:"tags"`
}

StringOrObject[T any]

Decodes a JSON string or a JSON object into T. When the value is a string, IsString is true. When it is an object, IsString is false and Object holds the decoded struct.

type StringOrObject[T any] struct {
    IsString bool
    String   string
    Object   T
}

// Handles both:
//   "author": "alice"
//   "author": {"id": 1, "name": "Alice"}
type Post struct {
    Author jsondec.StringOrObject[User] `json:"author"`
}

Forbidden fields

Forbidden

When a field is typed Forbidden, decoding stops with ErrForbiddenField if that key appears in the JSON. Use this for fields that are known to exist in the wire format but must never be accepted — deprecated fields, privilege escalation vectors, or keys reserved for internal use.

type CreateUserRequest struct {
    Name  string            `json:"name,required"`
    Email string            `json:"email,required"`
    Role  jsondec.Forbidden `json:"role"` // if this key appears, decoding fails — no caller may send it
}

Forbidden does not silently drop the field. It makes the entire decode call fail with ErrForbiddenField the moment that key is seen in the input. Use it for fields where accepting the value silently would be a security issue — privilege escalation vectors, deprecated fields that used to do something dangerous, or keys your validation layer has explicitly ruled out.


Decoder options

DecoderOptions is passed to RegisterDecoderOptions and applies to every call made by the returned decoder. The zero value gives the same behaviour as RegisterDecoder.

type DecoderOptions struct {
    DisallowUnknownFields bool
    MaxBytes              int
    MaxDepth              int
    MaxObjectFields       int
    MaxArrayLength        int
    ReuseInputBuffer      bool
}
Strictness

DisallowUnknownFields bool — Default false. When true, any JSON key with no matching struct field causes decoding to stop with ErrUnknownField. When false, unknown keys are skipped. Enable this for internal APIs where an unexpected field indicates a client bug; leave it off for public APIs where forward compatibility matters.

Structural limits

These limits protect against malicious or malformed input. They apply when the decoder is traversing portions of the document without decoding them: skipping unknown fields and preserving raw values. Nesting within decoded struct fields is not counted against MaxDepth.

MaxBytes int — Default 0 (disabled). Rejects the entire input document if its byte length exceeds this value, before any parsing begins. Also rejects any individual RawValue, RawObject, RawArray, or RawUnion field whose raw byte length exceeds this value at the point it is stored.

MaxDepth int — Default 0 (disabled). Rejects a value being skipped or preserved if its nesting depth exceeds this value. Prevents stack exhaustion from pathologically nested input.

MaxObjectFields int — Default 0 (disabled). Rejects a JSON object being skipped or preserved if it has more fields than this value.

MaxArrayLength int — Default 0 (disabled). Rejects a JSON array being skipped or preserved if it has more elements than this value.

Memory

ReuseInputBuffer bool — Default false.

When false (the default), jsondec copies the input []byte once before decoding into any type that can hold a reference to string or byte data — string, []byte, RawValue, RawObject, RawArray, RawUnion, map keys, and any. This copy means decoded values are safe to use after the input buffer is modified or freed.

When true, decoded strings and []byte fields point directly into the input slice — no copy is made. Two constraints follow: the caller must keep the input slice alive and unmodified for as long as any decoded value is in use, and jsondec may destructively modify the input bytes in place when unescaping strings containing backslash escape sequences.


Supported types

Go type Accepted JSON Notes
bool true, false
int, int8, int16, int32, int64 Number Integer only; ErrNumberOverflow if out of range for the target width
uint, uint8, uint16, uint32, uint64 Number Non-negative integer only
uintptr Number Decoded as uint64
float32, float64 Number
string String
[]byte String Raw string bytes — not base64-decoded; see the note in Why not encoding/json
time.Time String Must be RFC 3339 with nanoseconds (time.RFC3339Nano)
json.Number Number Preserved as the original string representation
Struct Object Fields matched by json tag, then Go field name
*T Any or null Null sets pointer to nil; non-null allocates T and decodes into it
[]T Array or null Null sets slice to nil
[N]T Array Input shorter than N zeroes remaining elements; longer returns ErrArrayTooLong
map[string]V Object or null Only string keys; null sets map to nil
interface{} Any Empty interface only — interface{ SomeMethod() } is not supported unless it also implements json.Unmarshaler. Objects → map[string]interface{}; arrays → []interface{}; numbers → float64
json.Unmarshaler Any Raw bytes passed to UnmarshalJSON
encoding.TextUnmarshaler String Decoded string passed to UnmarshalText; null zeroes the value

The following map types bypass reflection on every key and value assignment: map[string]string, map[string]int, map[string]int64, map[string]uint64, map[string]float64, map[string]bool.


One-off decode functions

These functions compile and cache type information on first call. They are intended for decoding types you do not control (and therefore cannot register at startup), and for custom json.Unmarshaler implementations that need to delegate specific fields back to jsondec.

DecodeInto[T any](raw []byte, dst *T) error

Decodes raw into dst with default options.

DecodeIntoOptions[T any](raw []byte, dst *T, opts DecoderOptions) error

Decodes raw into dst with the given options.

DecodeObject[T any](raw []byte, dst *T) error

Decodes raw into dst. Returns ErrExpectedObject if raw is not a JSON object or null.

DecodeArray[T any](raw []byte, dst *[]T) error

Decodes raw into dst. Returns ErrExpectedArray if raw is not a JSON array or null.

DecodeString(raw []byte) (string, error)

Decodes raw as a JSON string. Returns ErrTrailingData if non-whitespace follows the closing quote.

DecodeStringSlice(raw []byte) ([]string, error)

Decodes raw as a JSON array of strings.

DecodeStringEnum(raw []byte, allowed ...string) (string, error)

Decodes raw as a JSON string and returns ErrInvalidLiteral if the value is not in allowed.


Inspection functions

These read raw JSON bytes without decoding into Go types.

Kind(raw []byte) JSONKind

Returns the top-level kind of raw after skipping leading whitespace. Does not fully validate the value — Kind returning KindObject means the first non-whitespace byte is {, not that the object is well-formed.

Valid(raw []byte) bool

Reports whether raw is exactly one complete, valid JSON value with only whitespace following it. Full validation.

IsNull(raw []byte) bool, IsObject(raw []byte) bool, IsArray(raw []byte) bool

Convenience wrappers around Kind.


Errors

Runtime errors

All decode functions return jsondec.Error on failure.

type Error struct {
    Code   ErrorCode
    Offset int    // byte position in the input where the error was detected
    Field  []byte // the field name being decoded when the error occurred, if known
    Path   string // dot-notation path to the field, if known
}

Path uses dot notation for nested struct fields and bracket notation for array indices and quoted field names: "order.items[2].price", "metadata[\"x-custom\"]". It is populated only when an error propagates up through at least one struct or array level — errors at the top level have an empty path.

To inspect the error code:

var e jsondec.Error
if errors.As(err, &e) {
    switch e.Code {
    case jsondec.ErrRequiredFieldMissing:
        // e.Field contains the field name
        // e.Path contains the dot-notation path
    case jsondec.ErrUnknownField:
        // e.Field contains the unexpected key
    case jsondec.ErrValueTooLarge:
        // e.Offset is where the oversized value started
    }
}
Compile errors

RegisterDecoder and RegisterDecoderOptions panic with CompileError if T cannot be compiled. This happens at startup, not at decode time.

type CompileError struct {
    Type  reflect.Type // the type that failed
    Field string       // the struct field that caused the failure, if applicable
    Err   error        // the underlying reason
}

Common causes: unsupported field type, duplicate json field names in the same struct, more than 64 required fields.

Error codes
Code Meaning
ErrUnexpectedEOF Input ended before the value was complete
ErrExpectedObject Expected {
ErrExpectedArray Expected [
ErrExpectedString Expected "
ErrExpectedColon Expected : after an object key
ErrExpectedCommaOrEnd Expected , or a closing bracket
ErrInvalidString String contains a control character or invalid UTF-8
ErrInvalidEscape Unrecognised \X escape sequence
ErrInvalidUnicodeEscape \uXXXX is malformed or forms an invalid surrogate pair
ErrInvalidNumber Number is not valid JSON
ErrNumberOverflow Number is out of range for the destination type
ErrInvalidLiteral Unrecognised literal (not true, false, or null)
ErrInvalidNull null appeared where the destination type does not accept it
ErrRequiredFieldMissing A ,required field was absent from the JSON object
ErrArrayTooLong Array had more elements than the fixed-size Go array destination, or exceeded MaxArrayLength
ErrTrailingData Non-whitespace bytes followed the top-level value
ErrNilDestination dst was nil
ErrUnsupportedType No decoder exists for the destination type
ErrUnknownField Unknown key encountered with DisallowUnknownFields set
ErrValueTooLarge Input or a raw field value exceeded MaxBytes
ErrMaxDepth Nesting exceeded MaxDepth while skipping or preserving a value
ErrObjectTooLarge Object exceeded MaxObjectFields while skipping or preserving
ErrForbiddenField A key typed Forbidden appeared in the JSON object
ErrExpectedStringOrArray StringOrSlice field received neither a string nor an array
ErrExpectedStringOrObject StringOrObject field received neither a string nor an object

Documentation

Overview

Package jsondec provides a reflection-at-registration JSON decoder optimized for repeated decoding into structs.

Usage:

type User struct {
    ID   int    `json:"id,required"`
    Name string `json:"name"`
    Tags []int  `json:"tags"`
}

var DecodeUser = jsondec.RegisterDecoder[User]()

var u User
err := DecodeUser(rawJSON, &u)

Reflection is used when RegisterDecoder is called. The returned decoder uses unsafe offsets and custom scanning on its normal primitive, struct, pointer, slice, and array paths. Runtime reflection is used only for cases that need dynamic Go type operations: arbitrary map assignment, custom unmarshaler interface calls, arbitrary pointer allocation for non-specialized dynamic types, arbitrary composite slice allocation, and fixed-array tail zeroing for element types without a fast specialized zero path.

String scanning uses a two-stage design inspired by encoding/json/v2's jsonwire.ConsumeSimpleString: a tiny inlinable fast path with a 256-byte lookup table handles ordinary ASCII strings in one indexed load per byte, and the full scanner runs only when an escape, control byte, or high byte is encountered.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DecodeArray

func DecodeArray[T any](raw []byte, dst *[]T) error

DecodeArray decodes raw as an array or null into dst. The destination type controls whether null is accepted.

func DecodeInto

func DecodeInto[T any](raw []byte, dst *T) error

DecodeInto decodes raw into dst using jsondec's registered type compiler. It is intended for custom union decoders that need access to jsondec mechanics without falling back to encoding/json.

func DecodeIntoOptions

func DecodeIntoOptions[T any](raw []byte, dst *T, opts DecoderOptions) error

DecodeIntoOptions is DecodeInto with opt-in decoder options.

func DecodeObject

func DecodeObject[T any](raw []byte, dst *T) error

DecodeObject decodes raw as an object or null into dst. The destination type controls whether null is accepted.

func DecodeString

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

DecodeString decodes raw as a JSON string and rejects trailing non-whitespace.

func DecodeStringEnum

func DecodeStringEnum(raw []byte, allowed ...string) (string, error)

DecodeStringEnum decodes raw as a string and validates it against allowed.

func DecodeStringSlice

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

DecodeStringSlice decodes raw as []string using jsondec's scanner.

func IsArray

func IsArray(raw []byte) bool

IsArray reports whether raw's top-level token is a JSON array.

func IsNull

func IsNull(raw []byte) bool

IsNull reports whether raw's top-level token is JSON null.

func IsObject

func IsObject(raw []byte) bool

IsObject reports whether raw's top-level token is a JSON object.

func Valid

func Valid(raw []byte) bool

Valid reports whether raw is exactly one valid JSON value with only trailing whitespace after it.

Types

type CompileError

type CompileError struct {
	Type  reflect.Type
	Field string
	Err   error
}

CompileError is returned via panic from RegisterDecoder when T cannot be compiled.

func (CompileError) Error

func (e CompileError) Error() string

type DecodeFunc

type DecodeFunc[T any] func(raw []byte, dst *T) error

DecodeFunc decodes JSON bytes into dst.

func RegisterDecoder

func RegisterDecoder[T any]() DecodeFunc[T]

RegisterDecoder reflects over T once and returns a reusable, concurrent-safe decoder. T may be a struct or a pointer to a supported value type. The common decode paths avoid reflection; see the package comment for the remaining fallback cases.

func RegisterDecoderOptions

func RegisterDecoderOptions[T any](opts DecoderOptions) DecodeFunc[T]

RegisterDecoderOptions is RegisterDecoder with opt-in decoding behavior such as strict unknown-field rejection and structural limits. Strictness is an option rather than a separate registration function so callers have a single registration surface.

type DecoderOptions

type DecoderOptions struct {
	// DisallowUnknownFields returns ErrUnknownField instead of skipping unknown
	// struct fields. The default is false.
	DisallowUnknownFields bool

	// MaxBytes rejects an input document, or an individual RawValue/RawObject/
	// RawArray/RawUnion value, whose raw JSON byte length exceeds this value.
	// A zero or negative value disables the check.
	MaxBytes int

	// MaxDepth rejects values nested deeper than this value when the scanner is
	// skipping or preserving opaque/raw JSON. A zero or negative value disables
	// the check.
	MaxDepth int

	// MaxObjectFields rejects skipped or raw JSON objects with more fields than
	// this value. A zero or negative value disables the check.
	MaxObjectFields int

	// MaxArrayLength rejects skipped or raw JSON arrays with more elements than
	// this value. A zero or negative value disables the check.
	MaxArrayLength int

	// ReuseInputBuffer makes decoded string and []byte values alias raw directly.
	// When a retained string contains escapes, the decoder may destructively
	// compact/unescape raw in place, so raw may no longer contain the original
	// JSON after Decode returns. The caller must keep raw alive and immutable
	// while decoded values are live. The default false copies raw once when the
	// target type can retain strings or []byte values.
	ReuseInputBuffer bool
}

DecoderOptions configures an opt-in decoder variant. The zero value preserves RegisterDecoder's compatibility and performance defaults: unknown struct fields are ignored, path strings are allocated only when errors are wrapped, and no structural size limits are enforced.

type Error

type Error struct {
	Code   ErrorCode
	Offset int
	Field  []byte
	Path   string
}

Error is the compact error returned by decoders. It formats text only when Error is called.

func (Error) Error

func (e Error) Error() string

type ErrorCode

type ErrorCode uint8

ErrorCode is a compact parse or registration-independent error code.

const (
	ErrUnexpectedEOF ErrorCode = iota + 1
	ErrExpectedObject
	ErrExpectedArray
	ErrExpectedString
	ErrExpectedColon
	ErrExpectedCommaOrEnd
	ErrInvalidString
	ErrInvalidEscape
	ErrInvalidUnicodeEscape
	ErrInvalidNumber
	ErrNumberOverflow
	ErrInvalidLiteral
	ErrInvalidNull
	ErrRequiredFieldMissing
	ErrArrayTooLong
	ErrTrailingData
	ErrNilDestination
	ErrUnsupportedType
	ErrUnknownField
	ErrValueTooLarge
	ErrMaxDepth
	ErrObjectTooLarge
	ErrExpectedStringOrArray
	ErrExpectedStringOrObject
	ErrForbiddenField
)

type Forbidden

type Forbidden struct{}

Forbidden marks a known JSON field as explicitly forbidden. If the field is present, decoding stops with ErrForbiddenField.

type JSONKind

type JSONKind uint8

JSONKind is the top-level JSON token kind returned by Kind and RawUnion.

const (
	KindInvalid JSONKind = iota
	KindNull
	KindObject
	KindArray
	KindString
	KindNumber
	KindBool
)

func Kind

func Kind(raw []byte) JSONKind

Kind returns the top-level JSON kind after leading whitespace. It does not fully validate the value; use Valid to check complete JSON validity.

type Nullable

type Nullable[T any] struct {
	Null  bool
	Value T
}

Nullable records explicit JSON null separately from a non-null decoded Value. It does not by itself distinguish omitted fields; use OptionalNullable for omitted-vs-null-vs-value compatibility fields.

type Optional

type Optional[T any] struct {
	Present bool
	Value   T
}

Optional records whether a JSON field was present. If present, Value is decoded using the normal rules for T; explicit null is accepted only when T's own decoder accepts null.

type OptionalNullable

type OptionalNullable[T any] struct {
	Present bool
	Null    bool
	Value   T
}

OptionalNullable records omitted, explicit null, and non-null decoded value.

type Present

type Present struct {
	Present bool
	Null    bool
}

Present records whether a JSON field was present and whether it was explicit JSON null. Non-null values are validated and skipped without being stored.

type RawArray

type RawArray struct {
	Bytes   []byte
	Present bool
}

RawArray stores a validated JSON array or null without decoding it.

type RawObject

type RawObject struct {
	Bytes   []byte
	Present bool
}

RawObject stores a validated JSON object or null without decoding it. It is useful for schema-like extension payloads such as JSON Schema, metadata, and tool configuration objects.

type RawUnion

type RawUnion struct {
	Kind  JSONKind
	Bytes []byte
}

RawUnion stores any validated JSON value plus its top-level kind. It is a low-level building block for API-layer union fields that need procedural semantic handling after parse.

type RawValue

type RawValue struct {
	Bytes   []byte
	Present bool
}

RawValue stores a validated JSON value without decoding it into Go maps, slices, or primitives. Bytes preserves the original raw JSON bytes for the value, including whitespace within the value. Present is set when the value was decoded as a struct field or root value.

type StringOrObject

type StringOrObject[T any] struct {
	IsString bool
	String   string
	Object   T
}

StringOrObject decodes either a JSON string or an object T.

type StringOrSlice

type StringOrSlice[T any] struct {
	IsString bool
	String   string
	Slice    []T
}

StringOrSlice decodes either a JSON string or an array of T.

Jump to

Keyboard shortcuts

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