jsonc

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: May 3, 2026 License: MIT Imports: 20 Imported by: 0

README

go-rotini/jsonc

A Go JSONC encoding and decoding package that implements the JSONC specification, is JWCC-compatible (line + block comments, optional trailing commas), and is backed by the JSON Test Suite conformance tests as a drop-in alternative for encoding/json.

This package is used as the default JSONC support package for rotini.

Features

  • Full JSONC support: // line comments, /* ... */ block comments, trailing commas
  • JWCC-superset by default; opt-in WithStrictJSON for RFC 8259 conformance
  • Drop-in compatibility with encoding/json for the standard JSON subset (identical struct tags, json.Marshaler/json.Unmarshaler honored, json.Number/json.RawMessage work in fields)
  • Comment-preserving round-trips through the AST and RawValue
  • Generic UnmarshalTo[T] / MarshalTo[T] APIs and type-safe custom marshaler/unmarshaler registration
  • Streaming Encoder / Decoder matching encoding/json semantics (More(), InputOffset())
  • Struct field tags: omitempty, string (stdlib), omitzero, commented, required, default=<value> (jsonc extensions)
  • comment:"text" separate tag for emitting head comments
  • Encode options: indent, HTML escape, array multiline, comments, trailing commas, custom marshalers
  • Decode options: strict mode, strict-JSON mode, ordered maps, defaults, validators, custom unmarshalers, UseNumber
  • AST access via Parse, Walk, Filter, and Node tree manipulation with NodeToBytes
  • Path query engine with two syntaxes: rotini-style $.foo[0] (PathString) and RFC 6901 JSON Pointer /foo/0 (PathPointer)
  • RFC 6902 JSON Patch application via Patch.Apply
  • Whole-document helpers: Format, Minimize, ToJSON / StripComments
  • Valid function for quick syntax validation without full decoding
  • FormatError for human-readable error output with source line and column pointer
  • Context-aware encoding/decoding via EncodeContext / DecodeContext
  • MapSlice / MapItem for ordered map support
  • Deferred decoding with RawValue (interoperates with json.RawMessage)
  • DecodeFile / EncodeFile convenience for reading and writing JSONC files
  • DoS protection: depth limiting, key count limiting, document size limiting, node count limiting

Installation

go get github.com/go-rotini/jsonc

Requires Go 1.26 or later.

Quick Start

package main

import (
	"fmt"
	"log"

	"github.com/go-rotini/jsonc"
)

type Config struct {
	Title    string   `json:"title"`
	Server   Server   `json:"server"`
	Database Database `json:"database"`
}

type Server struct {
	Host string `json:"host"`
	Port int    `json:"port"`
}

type Database struct {
	Hosts   []string `json:"hosts"`
	Enabled bool     `json:"enabled"`
}

func main() {
	// Decode JSONC (with comments and trailing commas) into a struct.
	src := []byte(`{
		// Top-level configuration
		"title": "Example",
		"server": {
			"host": "localhost",
			"port": 8080,  /* default port */
		},
		"database": {
			"hosts": ["db1", "db2", "db3",],
			"enabled": true,
		},
	}`)

	var cfg Config
	if err := jsonc.Unmarshal(src, &cfg); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%+v\n", cfg)

	// Generic unmarshal (no pointer required).
	cfg2, err := jsonc.UnmarshalTo[Config](src)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%+v\n", cfg2)

	// Marshal with two-space indent.
	out, err := jsonc.MarshalIndent(cfg, "  ")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(out))
}

Documentation

Full API reference is available on pkg.go.dev.

Contributing

See CONTRIBUTING.md for guidelines on how to contribute to this project.

Code of Conduct

This project follows a code of conduct to ensure a welcoming community. See CODE_OF_CONDUCT.md.

Security

To report a vulnerability, see SECURITY.md.

License

This project is licensed under the MIT License. See LICENSE for details.

Documentation

Overview

Package jsonc implements JSONC (JSON with Comments) encoding and decoding.

JSONC extends RFC 8259 JSON with two human-friendly features:

  • C-style comments — single-line (//) and block (/* ... */)
  • Optional trailing commas in arrays and objects (per JWCC)

Every valid JSON document is a valid JSONC document. The encoder produces standard JSON by default; comments and trailing commas are opt-in via WithComment and WithTrailingComma, or carried through from a parsed AST.

The API follows the conventions of encoding/json: use Marshal and Unmarshal for one-shot conversions, Encoder and Decoder for streaming, and struct field tags to control mapping between JSONC keys and Go fields.

For low-level AST access, Parse returns a File containing a Node tree that can be inspected, mutated with Path queries, and re-serialized with NodeToBytes.

Drop-In Compatibility With encoding/json

For inputs that are valid RFC 8259 JSON, this package is a drop-in replacement for encoding/json: identical struct tags, identical Marshal and Unmarshal semantics, identical interface contracts (encoding/json.Marshaler and encoding/json.Unmarshaler are honored, encoding/json.Number and encoding/json.RawMessage work in fields). The standard tag is "json"; an additional "jsonc" tag is recognized for jsonc-specific options.

Notable defaults that differ from encoding/json:

  • HTML escaping is off by default (use WithEscapeHTML for parity).
  • time.Duration encodes as int64 nanoseconds, matching encoding/json (use WithDurationAsString for "1h30m"-style output).

JSONC Extensions

On top of the JSON grammar, this package accepts:

{
    // line comment to end of line
    "key": "value", /* block comment */
    "list": [1, 2, 3,], // trailing comma
}

Comments are scanned, attached to nearby AST nodes (HeadComment, Comment, FootComment), and preserved through Format/Minimize and AST round-trips. Trailing commas are accepted and silently dropped from RFC 8259 output. Use WithStrictJSON to reject these extensions and require strict JSON.

Type Mapping

Decode (JSONC → Go) supports:

JSONC string  → string, []byte (base64), time.Time, time.Duration,
                Number, encoding.TextUnmarshaler, any
JSONC number  → all integer/float types, *big.Int, *big.Float, Number,
                time.Duration (int64 nanoseconds), any
JSONC bool    → bool, any
JSONC null    → nil pointer/interface, zero value of any other type
JSONC array   → slice, fixed-size array, []any
JSONC object  → struct, map[K]V (K = string, integer, or
                TextUnmarshaler), map[string]any, [MapSlice]

Encode (Go → JSONC) is the inverse, with Marshaler / MarshalerContext / encoding/json.Marshaler / encoding.TextMarshaler dispatched in priority order before default reflection.

Struct Tags

Struct fields may be annotated with "json" tags (recommended for stdlib compatibility) or "jsonc" tags (for jsonc-specific options). When both are present, the field name comes from "jsonc" if specified, else "json"; the option lists are layered.

type Config struct {
    Name     string `json:"name,omitempty"`
    Email    string `json:"email" jsonc:",required"`
    Port     int    `jsonc:"port,default=8080"`
    Internal string `json:"-"`
}

The "json" tag honors stdlib options: omitempty, string, -. The "jsonc" tag adds: omitzero, commented, required, default=<value>. A separate "comment" tag attaches a head comment to the field:

type Server struct {
    Port int `json:"port" comment:"Listening port"`
}

Custom Marshalers

Types can implement Marshaler, MarshalerContext, Unmarshaler, BytesUnmarshaler, or UnmarshalerContext for jsonc-aware serialization. Stdlib encoding/json.Marshaler and encoding/json.Unmarshaler are also honored at lower priority.

Strict Modes

Two independent strictness modes are supported:

Whole-Document Transformations

Format pretty-prints JSONC, preserving comments and trailing commas with configurable indentation. Minimize compacts JSONC, also preserving comments. ToJSON (and its alias StripComments) strips comments and trailing commas to produce strict RFC 8259 JSON, useful when piping into systems that only accept stdlib JSON:

std, err := jsonc.ToJSON(data)
if err != nil { return err }
return json.Unmarshal(std, &v)

Path Queries

PathString compiles a rotini-style expression ($, .name, ["name"], [N], [*], ..name) into a Path that selects nodes from an AST. PathPointer compiles an RFC 6901 JSON Pointer. Once compiled, a Path supports Path.Read / Path.ReadFirst / Path.ReadString, plus structural mutations Path.Replace, Path.Append, and Path.Delete.

JSON Patch

ParsePatch decodes an RFC 6902 JSON Patch document into a Patch, and Patch.Apply applies all operations (add, remove, replace, move, copy, test) atomically against an AST. The input AST is never mutated — Apply works on a deep clone.

Raw Values

RawValue holds undecoded JSONC bytes, analogous to encoding/json.RawMessage. Use it as a struct field to defer decoding, or to round-trip arbitrary nested JSONC without losing comments and trailing commas. Call RawValue.Standardize to convert the raw bytes to RFC 8259 JSON.

Error Handling

Decoding errors are returned as typed values that support errors.Is:

if errors.Is(err, jsonc.ErrSyntax) { ... }
if errors.Is(err, jsonc.ErrDuplicateKey) { ... }

Use FormatError to produce a human-readable error with a source pointer.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrSyntax indicates malformed JSONC input.
	ErrSyntax = &SyntaxError{}
	// ErrType indicates one or more JSONC values could not be assigned to the target Go types.
	ErrType = &TypeError{}
	// ErrUnknownField indicates a JSONC key has no corresponding struct field (with [WithStrict]).
	ErrUnknownField = &UnknownFieldError{}
	// ErrDuplicateKey indicates an object key was defined more than once.
	ErrDuplicateKey = &DuplicateKeyError{}
	// ErrValidation indicates a [StructValidator] rejected a decoded struct.
	ErrValidation = &ValidationError{}
	// ErrDefault indicates a default struct tag value could not be applied.
	ErrDefault = &DefaultError{}
	// ErrOverflow indicates a JSONC number overflows the target Go type.
	ErrOverflow = &OverflowError{}
	// ErrStrictJSON indicates a JSONC extension (comment or trailing comma)
	// was encountered while [WithStrictJSON] was enabled.
	ErrStrictJSON = &StrictJSONError{}

	// ErrPathSyntax indicates an invalid [Path] expression.
	ErrPathSyntax = errors.New("jsonc: invalid path syntax")
	// ErrPathNotFound indicates no node matched a [Path] query.
	ErrPathNotFound = errors.New("jsonc: path not found")
	// ErrPointerSyntax indicates an invalid RFC 6901 JSON Pointer expression.
	ErrPointerSyntax = errors.New("jsonc: invalid JSON Pointer syntax")
	// ErrPatchSyntax indicates an invalid RFC 6902 JSON Patch document.
	ErrPatchSyntax = errors.New("jsonc: invalid JSON Patch syntax")
	// ErrNilPointer indicates a nil pointer was passed where a non-nil pointer is required.
	ErrNilPointer = errors.New("jsonc: non-nil pointer required")
	// ErrDocumentSize indicates the input exceeds the configured [WithMaxDocumentSize] limit.
	ErrDocumentSize = errors.New("jsonc: document size exceeds limit")
	// ErrUnsupportedValue indicates a value cannot be encoded (Inf/NaN, channels,
	// functions, complex numbers, or cyclic data).
	ErrUnsupportedValue = errors.New("jsonc: unsupported value")
)

Sentinel errors for use with errors.Is.

Functions

func DecodeFile

func DecodeFile(path string, v any, opts ...DecodeOption) error

DecodeFile reads and decodes the JSONC file at path into v.

func EncodeFile

func EncodeFile(path string, v any, opts ...EncodeOption) error

EncodeFile encodes v as JSONC and writes the result to the given file path. The file is created (or truncated) with mode 0o644.

func Format

func Format(data []byte, opts ...EncodeOption) ([]byte, error)

Format pretty-prints JSONC input. Comments and trailing commas are preserved; the output uses two-space indentation by default. Pass additional EncodeOption values to customize the output (e.g., WithIndent, WithIndentN, WithTrailingComma).

Format is a whole-document transformation: the input must be a single JSONC value (with optional surrounding whitespace and comments). To produce strict RFC 8259 JSON, use ToJSON instead.

Example
package main

import (
	"fmt"

	"github.com/go-rotini/jsonc"
)

func main() {
	src := []byte(`{"a":1,"b":[1,2,3]}`)
	out, err := jsonc.Format(src)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println(string(out))
}
Output:
{
  "a": 1,
  "b": [
    1,
    2,
    3
  ]
}

func FormatError

func FormatError(data []byte, err error, color ...bool) string

FormatError returns a human-readable string for errors that carry a Position (such as SyntaxError, DuplicateKeyError, OverflowError, StrictJSONError, or ValidationError). The output includes the offending source line and a column pointer. For other error types it returns err.Error(). Set color to true to include ANSI color escape sequences.

func Marshal

func Marshal(v any) ([]byte, error)

Marshal encodes v as JSONC bytes using default options. The output is compact (no indent, no comments) — for human-readable output, use MarshalIndent or MarshalWithOptions with WithIndent.

v may be any Go value supported by the encoder; see the package documentation for the type-mapping table.

Example
package main

import (
	"fmt"

	"github.com/go-rotini/jsonc"
)

func main() {
	type Config struct {
		Port int    `json:"port"`
		Host string `json:"host"`
	}
	out, err := jsonc.Marshal(Config{Port: 8080, Host: "localhost"})
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println(string(out))
}
Output:
{"port": 8080, "host": "localhost"}

func MarshalIndent

func MarshalIndent(v any, indent string) ([]byte, error)

MarshalIndent is a convenience wrapper that produces multi-line output with the given indent string. Equivalent to MarshalWithOptions(v, WithIndent(indent)).

Example
package main

import (
	"fmt"

	"github.com/go-rotini/jsonc"
)

func main() {
	out, err := jsonc.MarshalIndent(map[string]int{"a": 1, "b": 2}, "  ")
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println(string(out))
}
Output:
{
  "a": 1,
  "b": 2
}

func MarshalTo

func MarshalTo[T any](v T, opts ...EncodeOption) ([]byte, error)

MarshalTo encodes v as JSONC and returns the bytes; the type parameter exists for symmetry with UnmarshalTo when the caller knows the value type at the call site.

func MarshalWithOptions

func MarshalWithOptions(v any, opts ...EncodeOption) ([]byte, error)

MarshalWithOptions encodes v as JSONC with the given options.

func Minimize

func Minimize(data []byte, opts ...EncodeOption) ([]byte, error)

Minimize compacts JSONC input by removing redundant whitespace. Comments are preserved by default; pass WithStrictJSONOutput(true) (equivalent to using ToJSON) to drop them. Trailing commas are dropped in compact output regardless.

Example
package main

import (
	"fmt"

	"github.com/go-rotini/jsonc"
)

func main() {
	src := []byte(`{
		"a": 1,
		"b": 2
	}`)
	out, err := jsonc.Minimize(src)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println(string(out))
}
Output:
{"a": 1, "b": 2}

func NodeToBytes

func NodeToBytes(n *Node) ([]byte, error)

NodeToBytes serializes a Node tree back into JSONC bytes using default encoding options. Comments and structure are preserved.

func NodeToBytesWithOptions

func NodeToBytesWithOptions(n *Node, opts ...EncodeOption) ([]byte, error)

NodeToBytesWithOptions serializes a Node tree back into JSONC bytes using the provided encoding options.

func StripComments

func StripComments(data []byte) ([]byte, error)

StripComments is an alias for ToJSON. Provided for discoverability — callers searching for "strip comments from JSON" find it readily.

Example
package main

import (
	"fmt"
	"strings"

	"github.com/go-rotini/jsonc"
)

func main() {
	src := []byte(`{"a": 1 /* drop me */, "b": 2 // and me
	}`)
	out, _ := jsonc.StripComments(src)
	fmt.Println(strings.Contains(string(out), "/*"))
	fmt.Println(strings.Contains(string(out), "//"))
}
Output:
false
false

func ToJSON

func ToJSON(data []byte) ([]byte, error)

ToJSON converts JSONC input to standard RFC 8259 JSON by removing comments and trailing commas. The structure and values are preserved unchanged; only the JSONC extensions are stripped.

This is the most common conversion when piping JSONC into stdlib encoding/json.Unmarshal:

std, err := jsonc.ToJSON(data)
if err != nil { return err }
return json.Unmarshal(std, &v)
Example
package main

import (
	"fmt"
	"strings"

	"github.com/go-rotini/jsonc"
)

func main() {
	src := []byte(`{
		// kept as standard JSON output
		"port": 8080,
		"hosts": ["a", "b",], // trailing comma
	}`)
	out, err := jsonc.ToJSON(src)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	// Output is valid RFC 8259 JSON.
	fmt.Println(strings.Contains(string(out), "//"))
	fmt.Println(strings.Contains(string(out), ",]"))
}
Output:
false
false

func Unmarshal

func Unmarshal(data []byte, v any) error

Unmarshal decodes JSONC data into v. The input may include line and block comments and trailing commas (per JWCC). For RFC 8259-strict behavior, use UnmarshalWithOptions with WithStrictJSON.

v must be a non-nil pointer to a Go value. The Go target may be any type supported by the decoder; see the package documentation for the type-mapping table.

Example
package main

import (
	"fmt"

	"github.com/go-rotini/jsonc"
)

func main() {
	src := []byte(`{
		// Server configuration
		"port": 8080,
		"host": "localhost",
	}`)

	type Config struct {
		Port int    `json:"port"`
		Host string `json:"host"`
	}
	var cfg Config
	if err := jsonc.Unmarshal(src, &cfg); err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Printf("%s:%d\n", cfg.Host, cfg.Port)
}
Output:
localhost:8080

func UnmarshalTo

func UnmarshalTo[T any](data []byte, opts ...DecodeOption) (T, error)

UnmarshalTo decodes JSONC data into a new value of type T.

Example
package main

import (
	"fmt"

	"github.com/go-rotini/jsonc"
)

func main() {
	src := []byte(`{"name": "alice", "age": 30}`)
	type Person struct {
		Name string `json:"name"`
		Age  int    `json:"age"`
	}
	p, err := jsonc.UnmarshalTo[Person](src)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println(p.Name, p.Age)
}
Output:
alice 30

func UnmarshalWithOptions

func UnmarshalWithOptions(data []byte, v any, opts ...DecodeOption) error

UnmarshalWithOptions decodes JSONC data into v with the given options.

func Valid

func Valid(data []byte) bool

Valid reports whether data is a valid JSONC document. It accepts JSONC extensions (comments, trailing commas) by default. To validate strict RFC 8259 JSON, use Parse with WithStrictJSON and check the error.

func Walk

func Walk(n *Node, fn WalkFunc)

Walk traverses the AST rooted at n in depth-first pre-order, calling fn for each node. If fn returns false, the node's children are not visited. Walk(nil, fn) is a no-op.

Types

type BytesUnmarshaler

type BytesUnmarshaler interface {
	UnmarshalJSONC(data []byte) error
}

BytesUnmarshaler is implemented by types that can decode themselves directly from raw JSONC bytes.

type Comment

type Comment struct {
	// Position is where this comment appears relative to its target node.
	Position CommentPosition
	// Text is the comment body without delimiters; the encoder picks the
	// // or /* */ form based on Position and on whether the text contains
	// a line terminator.
	Text string
}

Comment attaches a comment to a JSONC node identified by key path when encoding with WithComment.

type CommentPosition

type CommentPosition int

CommentPosition specifies where a Comment appears relative to its node.

const (
	HeadCommentPos CommentPosition = iota // before the node
	LineCommentPos                        // on the same line, after the value
	FootCommentPos                        // after the node, inside its container
)

type CommentStyle

type CommentStyle int

CommentStyle indicates whether a comment was written as a // line comment or a /* */ block comment in the original source.

const (
	LineCommentStyle  CommentStyle = iota // // ...
	BlockCommentStyle                     // /* ... */
)

type DecodeOption

type DecodeOption func(*decoderOptions)

DecodeOption configures the behavior of Unmarshal, UnmarshalWithOptions, UnmarshalTo, Decoder, and Parse.

func WithAllowDuplicateKeys

func WithAllowDuplicateKeys() DecodeOption

WithAllowDuplicateKeys allows duplicate object keys; the last value wins, matching encoding/json behavior. The default is to return a DuplicateKeyError.

func WithCustomUnmarshaler

func WithCustomUnmarshaler[T any](fn func(*T, []byte) error) DecodeOption

WithCustomUnmarshaler registers a function that decodes JSONC bytes into a value of type T, overriding the default decoding for that type. Custom unmarshalers run at lower priority than the Unmarshaler / BytesUnmarshaler / UnmarshalerContext / json.Unmarshaler interfaces — to override a type that already implements one of those, wrap it in a new type.

func WithDefaults

func WithDefaults() DecodeOption

WithDefaults enables applying default values from struct tags. Default values are specified with the "default=<value>" tag option (e.g., `jsonc:"port,default=8080"`). Without this option, default tags are ignored. Only scalar types are supported: string, bool, int/uint variants, float variants, and time.Duration.

func WithMaxDepth

func WithMaxDepth(n int) DecodeOption

WithMaxDepth limits the nesting depth of the decoded value (default 100). Deeply nested documents are rejected with a SyntaxError.

func WithMaxDocumentSize

func WithMaxDocumentSize(n int) DecodeOption

WithMaxDocumentSize rejects input that exceeds n bytes before parsing begins. Zero means no limit.

func WithMaxKeys

func WithMaxKeys(n int) DecodeOption

WithMaxKeys limits the total number of object keys the parser may encounter. Zero means no limit.

func WithMaxNodes

func WithMaxNodes(n int) DecodeOption

WithMaxNodes limits the total number of AST nodes the parser may create. Zero means no limit.

func WithOrderedMap

func WithOrderedMap() DecodeOption

WithOrderedMap causes decoding into any (interface{}) to produce MapSlice values for objects instead of map[string]any, preserving member order.

func WithStrict

func WithStrict() DecodeOption

WithStrict causes decoding to return an UnknownFieldError if a JSONC key does not correspond to any field in the target struct. Matches encoding/json.Decoder.DisallowUnknownFields.

func WithStrictJSON

func WithStrictJSON() DecodeOption

WithStrictJSON causes the parser to reject JSONC extensions (line and block comments, trailing commas), enforcing RFC 8259 conformance. The rejection is reported as a StrictJSONError.

func WithUseNumber

func WithUseNumber() DecodeOption

WithUseNumber causes the decoder to materialize numbers as Number rather than float64 when decoding into interface{}. Matches encoding/json.Decoder.UseNumber. The option has no effect when decoding into typed numeric Go fields — those always parse to the target type.

func WithValidator

func WithValidator(v StructValidator) DecodeOption

WithValidator registers a StructValidator that is called after each struct is fully decoded.

type Decoder

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

Decoder reads JSONC values from an input stream. It supports decoding multiple top-level values back-to-back from a single reader, matching encoding/json.Decoder semantics.

Example
package main

import (
	"fmt"
	"strings"

	"github.com/go-rotini/jsonc"
)

func main() {
	src := strings.NewReader(`1 2 3`)
	dec := jsonc.NewDecoder(src)
	var sum int
	for dec.More() {
		var n int
		if err := dec.Decode(&n); err != nil {
			fmt.Println("error:", err)
			return
		}
		sum += n
	}
	fmt.Println(sum)
}
Output:
6

func NewDecoder

func NewDecoder(r io.Reader, opts ...DecodeOption) *Decoder

NewDecoder creates a Decoder that reads from r.

func (*Decoder) Decode

func (dec *Decoder) Decode(v any) error

Decode reads the next JSONC value from the underlying reader and decodes it into v.

func (*Decoder) DecodeContext

func (dec *Decoder) DecodeContext(ctx context.Context, v any) error

DecodeContext reads the next JSONC value with the given context.

func (*Decoder) InputOffset

func (dec *Decoder) InputOffset() int64

InputOffset returns the byte offset (in the input stream) of the next value's first byte (after consumed bytes and whitespace/comments).

func (*Decoder) More

func (dec *Decoder) More() bool

More reports whether there is another JSONC value waiting to be decoded.

func (*Decoder) SetContext

func (dec *Decoder) SetContext(ctx context.Context)

SetContext sets the context used by subsequent Decoder.Decode calls.

type DefaultError

type DefaultError struct {
	// Field is the struct field name (or jsonc-tag name, when set).
	Field string
	// Message describes why the default could not be applied.
	Message string
	// Pos is the source position of the surrounding object, if known.
	Pos Position
}

DefaultError is returned when a default value from a struct tag cannot be applied (parse failure, overflow, or unsupported field type).

func (*DefaultError) Error

func (e *DefaultError) Error() string

func (*DefaultError) Is

func (e *DefaultError) Is(target error) bool

type DuplicateKeyError

type DuplicateKeyError struct {
	// Key is the duplicated object member name.
	Key string
	// Pos is the source position of the second (offending) occurrence.
	Pos Position
}

DuplicateKeyError is returned when an object key is defined more than once. By default duplicates are rejected; pass WithAllowDuplicateKeys to opt into stdlib-style last-wins behavior.

func (*DuplicateKeyError) Error

func (e *DuplicateKeyError) Error() string

func (*DuplicateKeyError) Is

func (e *DuplicateKeyError) Is(target error) bool

type EncodeOption

type EncodeOption func(*encoderOptions)

EncodeOption configures the behavior of Marshal, MarshalWithOptions, and Encoder.

func WithArrayMultiline

func WithArrayMultiline(b bool) EncodeOption

WithArrayMultiline emits arrays with one element per line, regardless of the indent setting. Without this option, arrays follow the compact-or- indented rule of the surrounding output.

func WithComment

func WithComment(comments map[string][]Comment) EncodeOption

WithComment attaches comments to nodes by path. Each key in the map is a path expression resolving the node to annotate; each value is a slice of Comment entries specifying position (HeadCommentPos / LineCommentPos / FootCommentPos) and text.

Path syntax mirrors the encoder's own path construction:

  • Object members use dot notation: "server.port".
  • Map entries use the string form of the key: "users.alice".
  • Array elements use bracket-with-index: "tags[0]" or "items[3].name".

Comments are emitted only when output is multi-line (indent is set) and not in WithStrictJSONOutput mode. In compact or strict-JSON-output modes the option is silently ignored.

Example
package main

import (
	"fmt"

	"github.com/go-rotini/jsonc"
)

func main() {
	type Server struct {
		Port int    `json:"port"`
		Host string `json:"host"`
	}
	s := Server{Port: 8080, Host: "localhost"}
	out, err := jsonc.MarshalWithOptions(s,
		jsonc.WithIndent("  "),
		jsonc.WithComment(map[string][]jsonc.Comment{
			"port": {
				{Position: jsonc.HeadCommentPos, Text: "Listening port"},
				{Position: jsonc.LineCommentPos, Text: "default"},
			},
			"host": {
				{Position: jsonc.FootCommentPos, Text: "Host configuration ends here."},
			},
		}),
	)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println(string(out))
}
Output:
{
  // Listening port
  "port": 8080, // default
  "host": "localhost"
  // Host configuration ends here.
}

func WithCustomMarshaler

func WithCustomMarshaler[T any](fn func(T) ([]byte, error)) EncodeOption

WithCustomMarshaler registers a function that encodes values of type T to JSONC bytes, overriding the default encoding for that type. Custom marshalers run at lower priority than the Marshaler / MarshalerContext / json.Marshaler interfaces — to override a type that already implements one of those interfaces, wrap it in a new type.

func WithDurationAsString

func WithDurationAsString(b bool) EncodeOption

WithDurationAsString encodes time.Duration values as a human-readable string (e.g., "1h30m") via Duration.String(). The default is to encode as int64 nanoseconds, matching encoding/json.

func WithEscapeHTML

func WithEscapeHTML(b bool) EncodeOption

WithEscapeHTML controls whether <, >, &, U+2028, and U+2029 are escaped as their \u00XX equivalents in string output. The default is false (clean output); set to true for parity with encoding/json, which escapes by default.

func WithIndent

func WithIndent(indent string) EncodeOption

WithIndent sets the per-level indentation string. An empty string (default) produces compact output. Common choices are " " (two spaces), " " (four spaces), or "\t" (tab).

func WithIndentN

func WithIndentN(n int) EncodeOption

WithIndentN is a convenience that sets the indentation to n spaces. Equivalent to WithIndent(strings.Repeat(" ", n)).

func WithMapKeyOrder

func WithMapKeyOrder(order MapKeyOrder) EncodeOption

WithMapKeyOrder controls how map keys are emitted by the encoder. The default is MapKeyOrderLexicographic.

func WithOmitEmpty

func WithOmitEmpty(b bool) EncodeOption

WithOmitEmpty omits struct fields and map entries whose values are the zero value for their type. Equivalent to adding ",omitempty" to every field tag.

func WithStrictJSONOutput

func WithStrictJSONOutput(b bool) EncodeOption

WithStrictJSONOutput ensures the encoded output is valid RFC 8259 JSON: no comments are emitted (any comments registered via WithComment or attached to AST nodes are silently dropped), and no trailing commas are emitted regardless of WithTrailingComma. Inf and NaN are already rejected in default mode; this option is a defense-in-depth guard for pipelines that must produce strict JSON.

The encode-side counterpart of the decode-side WithStrictJSON; the names differ because Go does not allow same-name option functions returning different types.

func WithTrailingComma

func WithTrailingComma(b bool) EncodeOption

WithTrailingComma appends a trailing comma after the last element of arrays and the last member of objects when output is multi-line. No effect on compact (single-line) output.

type Encoder

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

Encoder writes JSONC values to an output stream. It mirrors encoding/json.Encoder: each call to Encoder.Encode writes one value followed by a newline.

Example
package main

import (
	"bytes"
	"fmt"

	"github.com/go-rotini/jsonc"
)

func main() {
	var buf bytes.Buffer
	enc := jsonc.NewEncoder(&buf, jsonc.WithIndent("  "))
	for _, v := range []map[string]int{{"a": 1}, {"b": 2}} {
		if err := enc.Encode(v); err != nil {
			fmt.Println("error:", err)
			return
		}
	}
	fmt.Print(buf.String())
}
Output:
{
  "a": 1
}
{
  "b": 2
}

func NewEncoder

func NewEncoder(w io.Writer, opts ...EncodeOption) *Encoder

NewEncoder creates an Encoder that writes to w with the given options.

func (*Encoder) Encode

func (enc *Encoder) Encode(v any) error

Encode writes the JSONC encoding of v to the stream, followed by a newline. Any error from the underlying writer is returned.

func (*Encoder) EncodeContext

func (enc *Encoder) EncodeContext(ctx context.Context, v any) error

EncodeContext is like Encoder.Encode but uses ctx for any MarshalerContext dispatch encountered during encoding.

func (*Encoder) SetContext

func (enc *Encoder) SetContext(ctx context.Context)

SetContext sets the context used by subsequent Encoder.Encode calls.

func (*Encoder) SetEscapeHTML

func (enc *Encoder) SetEscapeHTML(b bool)

SetEscapeHTML controls HTML-character escaping. Default is false, matching the package-level Marshal default and differing from encoding/json.Encoder (which defaults to true).

func (*Encoder) SetIndent

func (enc *Encoder) SetIndent(indent string)

SetIndent configures the indent string for subsequent Encoder.Encode calls. Equivalent to applying WithIndent at construction time.

type File

type File struct {
	// Root is the top-level value parsed from the input.
	Root *Node
	// Warnings holds non-fatal advisory messages produced during parsing.
	// An empty slice (or nil) indicates a clean parse.
	Warnings []string
}

File is the result of parsing a JSONC byte stream. JSONC documents represent a single JSON value at the top level (typically an object, but JSON permits any value).

func Parse

func Parse(data []byte, opts ...DecodeOption) (*File, error)

Parse tokenizes and parses data into an AST. It accepts JSONC by default (line and block comments, optional trailing commas). Pass WithStrictJSON to reject those extensions and require RFC 8259 conformance. Limits like WithMaxDepth / WithMaxKeys / WithMaxNodes / WithMaxDocumentSize are honored at parse time.

type MapItem

type MapItem struct {
	// Key is the unescaped JSON object member name.
	Key string
	// Value is the decoded value for this member.
	Value any
}

MapItem is a single key-value pair within a MapSlice. JSON object keys are always strings, so MapItem.Key is typed as string (unlike the YAML and TOML packages which permit any-typed keys).

type MapKeyOrder

type MapKeyOrder int

MapKeyOrder controls how object keys are emitted by the encoder.

const (
	// MapKeyOrderLexicographic sorts native map[K]V keys lexicographically
	// when encoding. This matches the default behavior of
	// [encoding/json.Marshal].
	MapKeyOrderLexicographic MapKeyOrder = iota

	// MapKeyOrderInsertion preserves the order of [MapSlice] entries on
	// encode. It has no effect on native map[K]V values, which Go iterates
	// in randomized order — for those, the encoder always falls back to
	// lexicographic ordering for stability.
	MapKeyOrderInsertion
)

type MapSlice

type MapSlice []MapItem

MapSlice is an ordered slice of key-value pairs. It is used as the decoded representation of JSONC objects when WithOrderedMap is enabled, preserving the original member order that a plain map[string]any would lose.

type Marshaler

type Marshaler interface {
	MarshalJSONC() ([]byte, error)
}

Marshaler is implemented by types that can encode themselves into raw JSONC bytes. Existing encoding/json.Marshaler implementations are also honored at lower priority, so types that already implement that interface continue to work unchanged.

type MarshalerContext

type MarshalerContext interface {
	MarshalJSONC(ctx context.Context) ([]byte, error)
}

MarshalerContext is like Marshaler but receives a context, set via Encoder.EncodeContext.

type Node

type Node struct {
	Kind         NodeKind
	Key          string       // for KeyValueNode: the unescaped member name (empty otherwise)
	Value        string       // for scalar nodes: the unescaped string value (empty for containers)
	RawValue     string       // for scalar nodes: the source-form bytes (preserves quoting style)
	CommentStyle CommentStyle // for CommentNode: which comment style
	Children     []*Node      // for ObjectNode: KeyValueNode children. For ArrayNode: value children. For KeyValueNode: a single value child.
	Pos          Position
	Comment      string // line comment (on the same line, after the value)
	HeadComment  string // comment block before this node
	FootComment  string // comment block after the last child but before the closing delimiter (containers only)
}

Node is a JSONC AST node.

func Filter

func Filter(n *Node, fn func(*Node) bool) []*Node

Filter walks the AST rooted at n and returns all nodes for which fn returns true.

func (*Node) String

func (n *Node) String() string

String returns a concise human-readable representation. For scalars, the raw value text. For containers, a kind + child-count summary. For KeyValueNode, the member name.

func (*Node) Validate

func (n *Node) Validate() error

Validate checks structural invariants of the node tree: object key uniqueness, well-formed scalar nodes, KeyValueNode with exactly one child. Returns nil if the tree is valid, else the first violation encountered.

type NodeKind

type NodeKind int

NodeKind identifies the type of a JSONC Node in the AST.

const (
	ObjectNode   NodeKind = iota // a JSON object {...}
	ArrayNode                    // a JSON array [...]
	KeyValueNode                 // a member of an object: "key": value
	StringNode                   // a string scalar
	NumberNode                   // a number scalar
	BooleanNode                  // a boolean scalar
	NullNode                     // a null scalar
	CommentNode                  // an orphan comment
)

func (NodeKind) String

func (k NodeKind) String() string

String returns the lowercase JSON-ish name of the node kind, used in error messages and debugging output.

type Number

type Number = json.Number

Number is an alias for encoding/json.Number. It preserves the original source text of a JSONC number, allowing callers to convert to int64 or float64 on demand without losing precision.

The alias lets callers use a single type across both packages — values of type encoding/json.Number and jsonc.Number are interchangeable.

type OverflowError

type OverflowError struct {
	// Value is the offending number's source text.
	Value string
	// Type is the Go target type that could not hold Value.
	Type string
	// Pos is the source position of the number.
	Pos Position
}

OverflowError is returned when a JSONC number overflows the target Go type.

func (*OverflowError) Error

func (e *OverflowError) Error() string

func (*OverflowError) Is

func (e *OverflowError) Is(target error) bool

type Patch

type Patch []PatchOp

Patch is an RFC 6902 JSON Patch document — an ordered sequence of operations applied as a single transformation.

func ParsePatch

func ParsePatch(data []byte) (Patch, error)

ParsePatch decodes data (a JSONC array of operation objects) into a Patch. Comments and trailing commas in the input are tolerated; the operation values themselves preserve their JSONC source form.

func (Patch) Apply

func (p Patch) Apply(root *Node) (*Node, error)

Apply applies p to root in order, returning the resulting Node. The input root is not modified — Apply works on a deep clone. Any failure returns an error and leaves the (cloned) intermediate state unobservable to the caller.

Example
package main

import (
	"fmt"

	"github.com/go-rotini/jsonc"
)

func main() {
	doc := []byte(`{"a": 1, "b": 2}`)
	patch := []byte(`[
		{"op": "replace", "path": "/a", "value": 100},
		{"op": "add", "path": "/c", "value": 3}
	]`)

	f, _ := jsonc.Parse(doc)
	p, _ := jsonc.ParsePatch(patch)
	out, err := p.Apply(f.Root)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	bytes, _ := jsonc.NodeToBytes(out)
	fmt.Println(string(bytes))
}
Output:
{"a": 100, "b": 2, "c": 3}

type PatchOp

type PatchOp struct {
	// Op names the operation: "add", "remove", "replace", "move", "copy",
	// or "test".
	Op string
	// Path is the RFC 6901 JSON Pointer identifying the operation's target.
	Path string
	// From is the source pointer for "move" / "copy" operations.
	From string
	// Value is the operand for "add" / "replace" / "test" operations.
	Value *Node
}

PatchOp is a single operation in an RFC 6902 JSON Patch document.

type Path

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

Path is a compiled query that selects zero or more Node values from a JSONC AST. Paths can be built from a rotini-style expression (see PathString) or from an RFC 6901 JSON Pointer (see PathPointer).

A Path is immutable after construction; reuse compiled paths across queries against different documents.

func PathPointer

func PathPointer(ptr string) (Path, error)

PathPointer compiles an RFC 6901 JSON Pointer into a Path. Per the RFC, the empty string refers to the document root; non-empty pointers must start with `/`. Tokens use the standard escapes `~0` for `~` and `~1` for `/`. Numeric tokens applied to an array select an element by index; the special token `-` (one-past-end) is rejected by Path.Read but accepted by Path.Append for the JSON Patch `add` operation.

Returns ErrPointerSyntax (wrapped) for malformed pointers.

Example
package main

import (
	"fmt"

	"github.com/go-rotini/jsonc"
)

func main() {
	src := []byte(`{"users": [{"name": "alice"}, {"name": "bob"}]}`)
	f, _ := jsonc.Parse(src)

	p, _ := jsonc.PathPointer("/users/1/name")
	name, _ := p.ReadString(f.Root)
	fmt.Println(name)
}
Output:
bob

func PathString

func PathString(expr string) (Path, error)

PathString compiles a rotini-style path expression into a Path. The supported grammar is intentionally small:

$           — the root document (optional; default if expression starts with . or [)
.name       — exact object member by name (name follows JS-identifier rules)
["name"]    — exact object member, allowing arbitrary names
['name']    — same as above with single quotes
[N]         — 0-based array index (negative indices are not supported)
[*]         — wildcard: every direct child of the current container
..name      — recursive descent: every descendant whose key matches name

Whitespace inside brackets is allowed; outside brackets it is not.

Returns ErrPathSyntax (wrapped) for malformed expressions.

Example
package main

import (
	"fmt"

	"github.com/go-rotini/jsonc"
)

func main() {
	src := []byte(`{
		"server": {
			"port": 8080,
			"hosts": ["a.example", "b.example"]
		}
	}`)
	f, err := jsonc.Parse(src)
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	p, _ := jsonc.PathString("$.server.hosts[1]")
	host, err := p.ReadString(f.Root)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println(host)
}
Output:
b.example

func (Path) Append

func (p Path) Append(root, value *Node) error

Append appends value as a new child to every container matched by p. For an object, value must be a KeyValueNode. For an array, value is appended as an element.

Returns ErrPathNotFound if p matches no nodes, or a SyntaxError if the value kind doesn't fit the matched container kind.

func (Path) Delete

func (p Path) Delete(root *Node) error

Delete removes every node matched by p from its parent container. KeyValueNode matches are removed from their parent ObjectNode; element matches are removed from their parent ArrayNode.

Returns ErrPathNotFound if p matches no nodes.

func (Path) Read

func (p Path) Read(root *Node) []*Node

Read returns the Node values reachable by p starting from root, in document order. Returns nil when no nodes match.

func (Path) ReadFirst

func (p Path) ReadFirst(root *Node) (*Node, error)

ReadFirst returns the first Node reachable by p, or ErrPathNotFound.

func (Path) ReadPositions

func (p Path) ReadPositions(root *Node) []Position

ReadPositions returns the source Position of every node matched by p.

func (Path) ReadString

func (p Path) ReadString(root *Node) (string, error)

ReadString is a convenience for paths that select a string-valued node. Returns the unescaped string value of the first match, or ErrPathNotFound. Non-string scalars return their raw textual form.

func (Path) Replace

func (p Path) Replace(root, value *Node) error

Replace overwrites every node matched by p with a copy of value. When the match is a KeyValueNode (an object member), the value child is replaced; otherwise the matched node itself is overwritten in place.

Returns ErrPathNotFound if p matches no nodes.

func (Path) String

func (p Path) String() string

String returns the original expression used to build the path.

type Position

type Position struct {
	// Line is the 1-indexed line number; 0 means "no position".
	Line int
	// Column is the 1-indexed column number on Line.
	Column int
	// Offset is the 0-indexed byte offset from the start of the input.
	Offset int
}

Position identifies a location within JSONC source text.

func (Position) String

func (p Position) String() string

String returns the position formatted as "line:column".

type RawValue

type RawValue []byte

RawValue is raw JSONC that has not been decoded. It can be used to delay decoding or to pass through a JSONC value without interpreting it. Analogous to encoding/json.RawMessage.

RawValue includes any comments and whitespace that are syntactically inside the value's bounds. To strip comments and trailing commas, producing standard JSON, use RawValue.Standardize.

Example
package main

import (
	"fmt"
	"strings"

	"github.com/go-rotini/jsonc"
)

func main() {
	type Wrapper struct {
		Meta jsonc.RawValue `json:"meta"`
		Name string         `json:"name"`
	}
	src := []byte(`{"meta": {"k": 1, /* preserved */ "v": [1,2,3]}, "name": "x"}`)
	var w Wrapper
	if err := jsonc.Unmarshal(src, &w); err != nil {
		fmt.Println("error:", err)
		return
	}
	// RawValue keeps the source bytes verbatim, including comments.
	fmt.Println(strings.Contains(string(w.Meta), "preserved"))
}
Output:
true

func (RawValue) MarshalJSONC

func (r RawValue) MarshalJSONC() ([]byte, error)

MarshalJSONC returns the raw bytes verbatim. Implements Marshaler.

func (RawValue) Standardize

func (r RawValue) Standardize() ([]byte, error)

Standardize returns a copy of the raw bytes with comments and trailing commas removed, producing valid RFC 8259 JSON.

func (RawValue) Unmarshal

func (r RawValue) Unmarshal(v any, opts ...DecodeOption) error

Unmarshal decodes the raw JSONC value into v.

func (*RawValue) UnmarshalJSONC

func (r *RawValue) UnmarshalJSONC(data []byte) error

UnmarshalJSONC stores the raw bytes verbatim. Implements BytesUnmarshaler.

type StrictJSONError

type StrictJSONError struct {
	// Feature names the rejected extension, e.g. "// line comment" or
	// "trailing comma".
	Feature string
	// Pos is the source position where the extension appeared.
	Pos Position
}

StrictJSONError is returned when the input contains a JSONC extension (a comment or a trailing comma) while WithStrictJSON is enabled.

func (*StrictJSONError) Error

func (e *StrictJSONError) Error() string

func (*StrictJSONError) Is

func (e *StrictJSONError) Is(target error) bool

type StructValidator

type StructValidator interface {
	Struct(v any) error
}

StructValidator validates a struct after all fields have been decoded. Implement this interface to integrate with validation libraries.

type SyntaxError

type SyntaxError struct {
	// Message is a short human-readable description of the syntactic problem.
	Message string
	// Pos is the source position where the error was detected; Line == 0
	// when the error has no specific location.
	Pos Position
	// Token is the offending token text, when available.
	Token string
}

SyntaxError is returned when the JSONC input is malformed. Use errors.Is(err, ErrSyntax) to test for syntax errors generically.

func (*SyntaxError) Error

func (e *SyntaxError) Error() string

func (*SyntaxError) Is

func (e *SyntaxError) Is(target error) bool

type TypeError

type TypeError struct {
	// Errors holds one message per failed value-to-target conversion, each
	// already prefixed with "line N: " when a position is known.
	Errors []string
}

TypeError is returned when one or more JSONC values cannot be assigned to the target Go types. The decoder accumulates conversion failures rather than failing fast and returns them all at once via this type.

func (*TypeError) Error

func (e *TypeError) Error() string

func (*TypeError) Is

func (e *TypeError) Is(target error) bool

type UnknownFieldError

type UnknownFieldError struct {
	// Field is the JSONC key that did not match any struct field.
	Field string
	// Pos is the source position of the unrecognized key.
	Pos Position
}

UnknownFieldError is returned when decoding with WithStrict and a JSONC key has no corresponding struct field.

func (*UnknownFieldError) Error

func (e *UnknownFieldError) Error() string

func (*UnknownFieldError) Is

func (e *UnknownFieldError) Is(target error) bool

type Unmarshaler

type Unmarshaler interface {
	UnmarshalJSONC(unmarshal func(any) error) error
}

Unmarshaler is implemented by types that need structured access to their JSONC data during decoding. The unmarshal function decodes the JSONC value into the provided Go value, similar to Unmarshal.

type UnmarshalerContext

type UnmarshalerContext interface {
	UnmarshalJSONC(ctx context.Context, unmarshal func(any) error) error
}

UnmarshalerContext is like Unmarshaler but receives a context, set via Decoder.DecodeContext.

type ValidationError

type ValidationError struct {
	// Err is the error returned by the validator.
	Err error
	// Pos is the source position of the JSONC object that produced the
	// validated struct value.
	Pos Position
}

ValidationError wraps an error returned by a StructValidator with the Position of the JSONC node that was decoded into the struct. Err is available via errors.Unwrap for callers that want the underlying value.

func (*ValidationError) Error

func (e *ValidationError) Error() string

func (*ValidationError) Is

func (e *ValidationError) Is(target error) bool

func (*ValidationError) Unwrap

func (e *ValidationError) Unwrap() error

type WalkFunc

type WalkFunc func(n *Node) bool

WalkFunc is the callback for Walk. Return true to recurse into the node's children, or false to skip the subtree.

Jump to

Keyboard shortcuts

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