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:
- WithStrict — return UnknownFieldError if a JSONC key has no corresponding struct field (matches encoding/json's DisallowUnknownFields).
- WithStrictJSON — reject comments and trailing commas during parsing (RFC 8259 conformance). The encode-side counterpart is WithStrictJSONOutput.
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 ¶
- Variables
- func DecodeFile(path string, v any, opts ...DecodeOption) error
- func EncodeFile(path string, v any, opts ...EncodeOption) error
- func Format(data []byte, opts ...EncodeOption) ([]byte, error)
- func FormatError(data []byte, err error, color ...bool) string
- func Marshal(v any) ([]byte, error)
- func MarshalIndent(v any, indent string) ([]byte, error)
- func MarshalTo[T any](v T, opts ...EncodeOption) ([]byte, error)
- func MarshalWithOptions(v any, opts ...EncodeOption) ([]byte, error)
- func Minimize(data []byte, opts ...EncodeOption) ([]byte, error)
- func NodeToBytes(n *Node) ([]byte, error)
- func NodeToBytesWithOptions(n *Node, opts ...EncodeOption) ([]byte, error)
- func StripComments(data []byte) ([]byte, error)
- func ToJSON(data []byte) ([]byte, error)
- func Unmarshal(data []byte, v any) error
- func UnmarshalTo[T any](data []byte, opts ...DecodeOption) (T, error)
- func UnmarshalWithOptions(data []byte, v any, opts ...DecodeOption) error
- func Valid(data []byte) bool
- func Walk(n *Node, fn WalkFunc)
- type BytesUnmarshaler
- type Comment
- type CommentPosition
- type CommentStyle
- type DecodeOption
- func WithAllowDuplicateKeys() DecodeOption
- func WithCustomUnmarshaler[T any](fn func(*T, []byte) error) DecodeOption
- func WithDefaults() DecodeOption
- func WithMaxDepth(n int) DecodeOption
- func WithMaxDocumentSize(n int) DecodeOption
- func WithMaxKeys(n int) DecodeOption
- func WithMaxNodes(n int) DecodeOption
- func WithOrderedMap() DecodeOption
- func WithStrict() DecodeOption
- func WithStrictJSON() DecodeOption
- func WithUseNumber() DecodeOption
- func WithValidator(v StructValidator) DecodeOption
- type Decoder
- type DefaultError
- type DuplicateKeyError
- type EncodeOption
- func WithArrayMultiline(b bool) EncodeOption
- func WithComment(comments map[string][]Comment) EncodeOption
- func WithCustomMarshaler[T any](fn func(T) ([]byte, error)) EncodeOption
- func WithDurationAsString(b bool) EncodeOption
- func WithEscapeHTML(b bool) EncodeOption
- func WithIndent(indent string) EncodeOption
- func WithIndentN(n int) EncodeOption
- func WithMapKeyOrder(order MapKeyOrder) EncodeOption
- func WithOmitEmpty(b bool) EncodeOption
- func WithStrictJSONOutput(b bool) EncodeOption
- func WithTrailingComma(b bool) EncodeOption
- type Encoder
- type File
- type MapItem
- type MapKeyOrder
- type MapSlice
- type Marshaler
- type MarshalerContext
- type Node
- type NodeKind
- type Number
- type OverflowError
- type Patch
- type PatchOp
- type Path
- func (p Path) Append(root, value *Node) error
- func (p Path) Delete(root *Node) error
- func (p Path) Read(root *Node) []*Node
- func (p Path) ReadFirst(root *Node) (*Node, error)
- func (p Path) ReadPositions(root *Node) []Position
- func (p Path) ReadString(root *Node) (string, error)
- func (p Path) Replace(root, value *Node) error
- func (p Path) String() string
- type Position
- type RawValue
- type StrictJSONError
- type StructValidator
- type SyntaxError
- type TypeError
- type UnknownFieldError
- type Unmarshaler
- type UnmarshalerContext
- type ValidationError
- type WalkFunc
Examples ¶
Constants ¶
This section is empty.
Variables ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
Types ¶
type BytesUnmarshaler ¶
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 ¶
Decode reads the next JSONC value from the underlying reader and decodes it into v.
func (*Decoder) DecodeContext ¶
DecodeContext reads the next JSONC value with the given context.
func (*Decoder) InputOffset ¶
InputOffset returns the byte offset (in the input stream) of the next value's first byte (after consumed bytes and whitespace/comments).
func (*Decoder) SetContext ¶
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 ¶
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 ¶
EncodeContext is like Encoder.Encode but uses ctx for any MarshalerContext dispatch encountered during encoding.
func (*Encoder) SetContext ¶
SetContext sets the context used by subsequent Encoder.Encode calls.
func (*Encoder) SetEscapeHTML ¶
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 ¶
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 ¶
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 ¶
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.
type 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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
Read returns the Node values reachable by p starting from root, in document order. Returns nil when no nodes match.
func (Path) ReadFirst ¶
ReadFirst returns the first Node reachable by p, or ErrPathNotFound.
func (Path) ReadPositions ¶
ReadPositions returns the source Position of every node matched by p.
func (Path) ReadString ¶
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 ¶
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.
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.
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 ¶
MarshalJSONC returns the raw bytes verbatim. Implements Marshaler.
func (RawValue) Standardize ¶
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 ¶
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 ¶
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.
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 ¶
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