jsonc

package module
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Jul 24, 2026 License: MIT Imports: 8 Imported by: 0

README

go-jsonc

Go Reference Go Report Card CI

A Concrete Syntax Tree (CST) parser, serializer, formatter, and builder for JSONC (JSON with Comments) in Go.

go get github.com/fan92rus/go-jsonc

Why CST, not AST?

A CST preserves everything — every comment, every space, every formatting choice. When you parse a file, edit a value, and serialize it back, the original formatting and comments are preserved. This is essential for config file management where comments carry meaning.


Quick start

Build JSONC from scratch
doc := jsonc.Object(
	"host", "localhost",
	"port", 8080,
	"debug", true,
	"tags", jsonc.Array("dev", "test"),
)
doc.Set("mode", "strict").Set("port", 9090)

fmt.Println(jsonc.Format(doc, &jsonc.FormatOptions{Indent: "  "}))

Output:

{
  "host": "localhost",
  "port": 9090,
  "debug": true,
  "tags": [
    "dev",
    "test"
  ],
  "mode": "strict"
}
Parse, edit, format
src := `{"name": "Alice", "age": 30}`
doc, _ := jsonc.Parse(src)
obj := doc.Root() // skip Document → root Object

obj.Set("age", 31)
obj.Set("city", "Berlin")

fmt.Println(jsonc.Format(obj, nil))
// {
//   "name": "Alice",
//   "age": 31,
//   "city": "Berlin"
// }

Path navigation (dot paths)

Navigate into nested objects and arrays with dot-separated paths. Numeric segments index into Arrays — ideal for real config files.

doc, _ := jsonc.Parse(`{
  "outbounds": [
    {"tag": "proxy-1", "port": 443, "settings": {"tls": true}},
    {"tag": "proxy-2", "port": 8443}
  ]
}`)
root := doc.Root()

// Read
fmt.Println(root.GetPath("outbounds.0.tag").Value)       // "proxy-1"
fmt.Println(root.GetPath("outbounds.1.port").Value)       // 8443
fmt.Println(root.GetPath("outbounds.0.settings.tls").Value) // true

// Write
root.SetPath("outbounds.0.port", 8080)
root.SetPath("outbounds.0.settings.tls", false)

// Delete
root.DeletePath("outbounds.1")           // removes second element
fmt.Println(root.GetPath("outbounds"))   // only proxy-1 remains

// Struct bindings
type Outbound struct {
	Tag string `json:"tag"`
	Port int   `json:"port"`
}
var ob Outbound
root.UnmarshalPath("outbounds.0", &ob)   // → {proxy-1 8080}
Path operations cheat sheet
Expression Result
GetPath("port") Value of member "port"
GetPath("outbounds.0.tag") Value at Object→Array→Object→key
SetPath("timeout", 30) Add or update a top-level member
SetPath("items.0.port", 80) Replace array element's nested field
DeletePath("items.1") Remove an array element by index
DeletePath("feature") Remove a member
UnmarshalPath("outbound", &v) Navigate + deserialize into Go struct
MarshalPath("outbound", v) Serialize Go struct → replace subtree

Missing keys auto-vivify as Objects. Arrays must exist before indexing into them — SetPath("items.0", val) works when items is already an Array.


File operations

// Read a JSONC config file
doc, err := jsonc.ParseFile("/opt/etc/xkeen/xray/config.json")

// Navigate and modify
doc.Root().SetPath("outbounds.0.port", 8080)

// Write back (preserves comments and formatting)
err = doc.WriteFile("/opt/etc/xkeen/xray/config.json")

Struct binding (MarshalPath / UnmarshalPath)

Convert between JSONC subtrees and Go structs. The optional jsonc tag adds a line comment before the JSON member.

type Config struct {
	Host    string `json:"host"`              // required
	Port    int    `json:"port" jsonc:"Listen port"`
	Debug   bool   `json:"debug,omitempty"`
}

cfg := Config{Host: "localhost", Port: 9090}
doc := jsonc.Object()
doc.MarshalPath("server", cfg)

fmt.Println(jsonc.Format(doc, &jsonc.FormatOptions{Indent: "  "}))

Output:

{
  "server": {
    // Listen port
    "port": 9090,
    "host": "localhost"
  }
}

Read back:

var got Config
doc.UnmarshalPath("server", &got)
fmt.Println(got.Host) // localhost
jsonc tag styles
Tag Result
jsonc:"My comment" // My comment before the member
jsonc:"// Important" // Important
jsonc:"/* block */" /* block */ before the member

Building JSONC

Object(key, val, key, val, ...) wraps Go values automatically:

doc := jsonc.Object(
	"name",   "Alice",
	"age",    30,
	"active", true,
	"data",   nil,                     // → null
	"tags",   jsonc.Array("a", "b"),
	"meta",   jsonc.Object("key", "val"),
)

Array(elem, elem, ...) does the same for arrays:

arr := jsonc.Array("hello", 42, true, nil, 3.14, jsonc.Object("x", 1))

Supported value types: string, int/int64/float64, bool, nil, *Node (for pre-built or nested structures), []any, map[string]any.

Mutation API (fluent)
doc := jsonc.Object("a", 1)
doc.
	Set("b", 2).              // add member
	Set("a", 10).             // update existing
	Set("c", 3, "comment").   // with trailing comment
	Delete("b")               // remove member

v := doc.Get("a")             // value node for "a"
fmt.Println(doc.Has("c"))     // true
fmt.Println(doc.Keys())       // [a c]
fmt.Println(doc.Len())        // 2
Iteration
for _, key := range doc.Keys() {
	fmt.Println("key:", key)
}

for _, val := range doc.Values() {
	fmt.Println("value:", val.Value)
}

for _, m := range doc.Members() {
	fmt.Println(m.KeyNode().Value, "→", m.ValueNode().Value)
}
Extended API (verbose control)

When you need fine control over CST node placement:

obj := jsonc.NewObject(
	jsonc.NewCommentLine(" Auto-generated"),
	jsonc.NewMember("host", jsonc.NewString("localhost")),
	jsonc.NewMember("port", jsonc.NewNumber("8080"),
		jsonc.NewCommentLine(" default"),
	),
)

The compact constructors Object() and Array() are built on top of these primitives. Use the extended API when you need to insert comments, control node order explicitly, or mix in whitespace/trivia nodes.


Parsing

doc, err := jsonc.Parse(src)

Parses a JSONC string into a CST *Node. The root is always KindDocument. Access the root via doc.Root().

Valid JSON and JSONC (with // and /* */ comments) are both accepted. Invalid input produces KindError nodes in the tree rather than panicking.


Serialization

text := jsonc.Serialize(doc)

Serializes a CST back into source text. Lossless round-trip — identical to the original input for valid JSON/JSONC.


Formatting (pretty-print)

formatted := jsonc.Format(doc, &jsonc.FormatOptions{Indent: "  "})
Indent Style
" " (or nil) Two-space indent (default)
"\t" Tab indent
" " Four-space indent
"" Compact / unindented

Comments are preserved and properly positioned. Idempotent — re-formatting produces identical output.


Lower-level API

Scalar nodes
s  := jsonc.NewString("hello")   // → "hello"
n  := jsonc.NewNumber("42")      // → 42
b  := jsonc.NewBoolean(true)     // → true
nu := jsonc.NewNull()            // → null
lc := jsonc.NewCommentLine("hi") // → // hi
bc := jsonc.NewCommentBlock("x") // → /* x */
Tree traversal
// Walk depth-first (stop early by returning false)
doc.Walk(func(n *jsonc.Node) bool {
	fmt.Println(n.Kind, n.Value)
	return true
})

// Find by kind
strs := doc.FindAll(jsonc.KindString)
cmts := doc.FindAll(jsonc.KindComment)

// Compare subtrees
jsonc.DeepEqual(a, b) // true if same structure ignoring positions
Container children
for _, m := range obj.Members() { /* ... */ }
for _, e := range arr.Elements() { /* ... */ }

// Comment body (without delimiters)
fmt.Println(c.Body()) // "text without // or /* */"

Node kinds

Kind Description
KindDocument Root node
KindObject { }
KindArray [ ]
KindMember "key": value
KindString "..."
KindNumber 123, -1.5e10
KindBoolean true, false
KindNull null
KindComment //… or /*…*/
KindWhitespace Spaces, tabs, newlines
KindComma ,
KindColon :
KindLBrace / KindRBrace { / }
KindLBracket / KindRBracket [ / ]
KindError Error recovery node

Property-based testing

The test suite uses rapid for property-based testing with generators that produce random valid JSONC documents. Properties tested:

  • All valid JSON/JSONC parses without errors
  • Comment preservation (line, block, mixed, every position)
  • Parse → serialize identity and idempotence
  • Format preserves semantics across all indent styles
  • Format idempotence (re-formatting produces identical output)
  • Path navigation get/set/delete with PBT
  • Array index access with PBT round-trip
  • Struct serialization round-trip
  • Deep nesting (500+ levels)
  • Error recovery (truncated input, random bytes)
  • Trailing commas, Unicode, escape sequences, number variations

167+ passing tests, 0 lint issues (golangci-lint max-strict config, 22+ linters).


Project status

Stable v0.x — core API (parse, serialize, format, navigation, path access, struct binding, file I/O) is fully implemented and well-tested. Backward compatibility is guaranteed within the v0.x series.


Contributing

  1. Fork the repo
  2. Create a feature branch
  3. Run go test ./... and golangci-lint run ./...
  4. Submit a PR

License

MIT — see LICENSE.

Documentation

Overview

Package jsonc provides a concrete syntax tree (CST) parser, serializer, and formatter for JSONC (JSON with Comments).

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Format

func Format(doc *Node, opts *FormatOptions) string

Format pretty-prints a CST.

Example
package main

import (
	"fmt"
	"log"

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

func main() {
	src := `{"a":1,"b":2}`
	doc, err := jsonc.Parse(src)
	if err != nil {
		log.Fatal(err)
	}
	formatted := jsonc.Format(doc, &jsonc.FormatOptions{Indent: "  "})
	fmt.Println(formatted)
}
Output:
{
  "a": 1,
  "b": 2
}

func Serialize

func Serialize(doc *Node) string

Serialize converts a CST back to its source text.

Types

type CommentStyle

type CommentStyle int

CommentStyle distinguishes line comments from block comments.

const (
	CommentLine  CommentStyle = iota // // ...
	CommentBlock                     // /* ... */
)

Comment styles.

type FormatOptions

type FormatOptions struct {
	Indent string
}

FormatOptions controls pretty-printing behaviour.

Indent specifies the per-level indentation string. Examples:

  • " " — two-space indent (default)
  • "\t" — tab indent
  • " " — four-space indent
  • "" — compact/minified output (no indentation)

Zero value (FormatOptions{}) produces compact output because Indent="" is the empty string. To get the default two-space indent, pass

&FormatOptions{Indent: "  "}

or nil (Format(nil) uses two spaces).

type Node

type Node struct {
	Kind     NodeKind
	Children []*Node // For container nodes (Document, Object, Array, Member)
	Value    string  // Raw source text for leaf nodes (tokens, comments, whitespace)

	// Comment-specific fields
	CommentStyle CommentStyle // Only valid when Kind == KindComment
	CommentBody  string       // Content without delimiters (// or /* */)

	Start Position // Start position in source (inclusive)
	End   Position // End position in source (exclusive)
}

Node is a single node in the Concrete Syntax Tree.

A CST preserves ALL source information: values, structural tokens, comments, and whitespace. This enables lossless round-trip parsing (parse → serialize → identical output) and comment-aware formatting.

func Array

func Array(values ...any) *Node

Array builds a JSON array from Go values.

Example (Mixed)

Compact array with mixed types.

package main

import (
	"fmt"

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

func main() {
	doc := jsonc.Array("hello", 42, true, nil, 3.14)
	fmt.Print(jsonc.Format(doc, &jsonc.FormatOptions{Indent: ""}))
}
Output:
[
"hello",
42,
true,
null,
3.14
]

func NewArray

func NewArray(elements ...*Node) *Node

NewArray creates a new JSON array CST node. Commas are automatically inserted between the elements.

Example
package main

import (
	"fmt"

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

func main() {
	doc := jsonc.NewArray(
		jsonc.NewNumber("1"),
		jsonc.NewNumber("2"),
		jsonc.NewNumber("3"),
	)
	fmt.Println(jsonc.Format(doc, nil))
}
Output:
[
  1,
  2,
  3
]

func NewBoolean

func NewBoolean(val bool) *Node

NewBoolean creates a new JSON boolean value node.

func NewCommentBlock

func NewCommentBlock(body string) *Node

NewCommentBlock creates a new block comment node. The body is the text inside "/* */". Leading/trailing whitespace is trimmed from the body.

Example (Builder)
package main

import (
	"fmt"
	"strings"

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

func main() {
	doc := jsonc.NewObject(
		jsonc.NewMember("count", jsonc.NewNumber("42"),
			jsonc.NewCommentBlock("answer"),
		),
	)
	out := jsonc.Format(doc, &jsonc.FormatOptions{Indent: "  "})
	fmt.Println(strings.Contains(out, "answer"))
}
Output:
true

func NewCommentLine

func NewCommentLine(body string) *Node

NewCommentLine creates a new line comment node. The body is the text after "// ". Leading/trailing whitespace is trimmed from the body to avoid double spacing.

Example (Builder)
package main

import (
	"fmt"
	"strings"

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

func main() {
	doc := jsonc.NewObject(
		jsonc.NewMember("key", jsonc.NewString("value"),
			jsonc.NewCommentLine("note"),
		),
	)
	out := jsonc.Format(doc, &jsonc.FormatOptions{Indent: "  "})
	fmt.Println(strings.Count(out, "//"))
}
Output:
1

func NewMember

func NewMember(key interface{}, val *Node, extra ...*Node) *Node

NewMember creates a new JSON object member CST node ("key": value). key must be a KindString node or a string literal. val must be a value node (KindString, KindNumber, KindBoolean, KindNull, KindObject, KindArray). Extra nodes (comments, whitespace) are placed between the colon and the value — useful for inline comments.

func NewNull

func NewNull() *Node

NewNull creates a new JSON null value node.

func NewNumber

func NewNumber(value string) *Node

NewNumber creates a new JSON number value node.

func NewObject

func NewObject(items ...*Node) *Node

NewObject creates a new JSON object node with the given children. NewObject creates a new JSON object CST node. Commas are automatically inserted between the children.

Example
package main

import (
	"fmt"

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

func main() {
	doc := jsonc.NewObject(
		jsonc.NewMember("host", jsonc.NewString("localhost")),
		jsonc.NewMember("port", jsonc.NewNumber("8080")),
	)
	formatted := jsonc.Format(doc, &jsonc.FormatOptions{Indent: "  "})
	fmt.Print(formatted)
}
Output:
{
  "host": "localhost",
  "port": 8080
}

func NewString

func NewString(value string) *Node

NewString creates a new JSON string value node. The value is the raw JSON literal including surrounding quotes.

func Object

func Object(keyvals ...any) *Node

Object builds a JSON object from key-value pairs. Each argument pair is (string key, any value).

Supported value types:

  • string → quoted JSON string
  • int, int64 → number
  • float64 → number
  • bool → true/false
  • nil → null
  • *Node → used as-is (for pre-built / nested structures)
  • []any → Array (recursive)
  • map[string]any → Object (recursive, keys sorted)
Example

Compact constructor: Object takes key-value pairs.

package main

import (
	"fmt"

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

func main() {
	doc := jsonc.Object(
		"host", "localhost",
		"port", 8080,
	)
	fmt.Print(jsonc.Format(doc, &jsonc.FormatOptions{Indent: "  "}))
}
Output:
{
  "host": "localhost",
  "port": 8080
}
Example (Nested)

Nested Object and Array work recursively.

package main

import (
	"fmt"

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

func main() {
	doc := jsonc.Object(
		"server", jsonc.Object(
			"host", "example.com",
			"port", 443,
		),
		"tags", jsonc.Array("prod", "us-east"),
	)
	fmt.Print(jsonc.Format(doc, &jsonc.FormatOptions{Indent: "  "}))
}
Output:
{
  "server": {
    "host": "example.com",
    "port": 443
  },
  "tags": [
    "prod",
    "us-east"
  ]
}

func ObjectFromMembers added in v0.2.0

func ObjectFromMembers(members []*Node) *Node

ObjectFromMembers creates an Object CST node from a slice of Member nodes.

func Parse

func Parse(input string) (*Node, error)

Parse parses a JSONC document and returns its CST.

Example
package main

import (
	"fmt"
	"log"

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

func main() {
	src := `{"name": "hello", "value": 42}`
	doc, err := jsonc.Parse(src)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(doc.Kind)
}
Output:
Document
Example (Jsonc)
package main

import (
	"fmt"
	"log"

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

func main() {
	src := `{
  // user profile
  "name": "Alice",
  "age": 30 /* years */
}`
	doc, err := jsonc.Parse(src)
	if err != nil {
		log.Fatal(err)
	}
	comments := doc.FindAll(jsonc.KindComment)
	fmt.Println(len(comments))
}
Output:
2

func ParseFile added in v0.2.0

func ParseFile(path string) (*Node, error)

ParseFile reads a file and parses its content as JSONC.

func (*Node) AppendChild

func (n *Node) AppendChild(child *Node)

AppendChild appends a child node and updates Start/End positions.

func (*Node) Body

func (n *Node) Body() string

Body returns the body of a comment node. Returns empty string for non-comment nodes.

Example
package main

import (
	"fmt"

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

func main() {
	src := `{"a": 1 /* important */}`
	doc, _ := jsonc.Parse(src)
	for _, c := range doc.FindAll(jsonc.KindComment) {
		fmt.Println(c.Body())
	}
}
Output:
important

func (*Node) DeepEqual

func (n *Node) DeepEqual(other *Node) bool

DeepEqual checks structural equality: same kind, same value, same children.

func (*Node) Delete

func (n *Node) Delete(key string) *Node

Delete removes a member by key from an Object. Returns the receiver for fluent chaining.

Example

Delete: remove a member by key.

package main

import (
	"fmt"

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

func main() {
	doc := jsonc.Object("a", 1, "b", 2, "c", 3)
	doc.Delete("b")
	fmt.Print(jsonc.Format(doc, &jsonc.FormatOptions{Indent: ""}))
}
Output:
{
"a": 1,
"c": 3
}
Example (Chain)

Delete chain: fluent interface.

package main

import (
	"fmt"

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

func main() {
	doc := jsonc.Object("a", 1, "b", 2, "c", 3).
		Delete("a").
		Delete("c")
	fmt.Print(jsonc.Format(doc, &jsonc.FormatOptions{Indent: ""}))
}
Output:
{
"b": 2
}

func (*Node) DeletePath added in v0.2.0

func (n *Node) DeletePath(path string) *Node

DeletePath removes a member at a dot-separated path.

obj.DeletePath("xkeen.speed_balancer.interval")
arr.DeletePath("0")          // remove first array element

Numeric segments target Array elements by index. Intermediate access follows the same object/array rules as GetPath. Missing or out-of-range paths are a silent no-op. Returns the receiver for fluent chaining.

func (*Node) Elements

func (n *Node) Elements() []*Node

Elements returns the value element nodes of an Array, or nil.

func (*Node) FindAll

func (n *Node) FindAll(kind NodeKind) []*Node

FindAll returns all nodes matching the given kind.

func (*Node) FirstChild

func (n *Node) FirstChild() *Node

FirstChild returns the first non-trivia child node, or nil. In a CST, the first child may be whitespace or a comment; FirstChild skips those and returns the first meaningful node.

func (*Node) FirstChildOfKind

func (n *Node) FirstChildOfKind(kinds ...NodeKind) *Node

FirstChildOfKind returns the first child matching any of the given kinds.

func (*Node) Get

func (n *Node) Get(key string) *Node

Get returns the value node for a given key in an Object, or nil if not found.

Example

Getter: read a value by key.

package main

import (
	"fmt"

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

func main() {
	doc := jsonc.Object("host", "localhost", "port", 8080)
	fmt.Println(doc.Get("host").Value)
	fmt.Println(doc.Get("port").Value)
	fmt.Println(doc.Get("missing"))
}
Output:
"localhost"
8080
<nil>

func (*Node) GetPath added in v0.2.0

func (n *Node) GetPath(path string) *Node

GetPath navigates dot-separated keys from this node.

obj.GetPath("xkeen.speed_balancer.enabled")
arr.GetPath("0")         // first element of array
obj.GetPath("items.2")   // third element of items array

Numeric segments index into Arrays via Elements(). Object segments use Get() for key lookup. Mixed paths are supported. Returns nil when any intermediate segment is missing or wrong type.

func (*Node) Has

func (n *Node) Has(key string) bool

Has reports whether a member with the given key exists in an Object.

Example

Has: check if a key exists.

package main

import (
	"fmt"

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

func main() {
	doc := jsonc.Object("x", 10, "y", 20)
	fmt.Println(doc.Has("x"))
	fmt.Println(doc.Has("z"))
	fmt.Println(jsonc.Object().Has("x"))
	fmt.Println((*jsonc.Node)(nil).Has("x"))
	fmt.Println(jsonc.Array(1, 2).Has("x"))
}
Output:
true
false
false
false
false

func (*Node) IsContainer

func (n *Node) IsContainer() bool

IsContainer returns true for nodes that can have child value nodes.

func (*Node) IsTrivia

func (n *Node) IsTrivia() bool

IsTrivia returns true for whitespace and comment nodes.

func (*Node) IsValue

func (n *Node) IsValue() bool

IsValue returns true for nodes that represent JSON values.

func (*Node) KeyNode

func (n *Node) KeyNode() *Node

KeyNode returns the key (string) child of a Member node, or nil.

func (*Node) Keys

func (n *Node) Keys() []string

Keys returns the member keys of an Object in document order. Returns nil for nil, non-Object, or empty objects.

Example

Keys: list member keys in document order.

package main

import (
	"fmt"

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

func main() {
	doc := jsonc.Object("beta", 2, "alpha", 1, "gamma", 3)
	fmt.Println(doc.Keys())
	fmt.Println(jsonc.Object().Keys())
	fmt.Println((*jsonc.Node)(nil).Keys()) // nil slice prints as []
	fmt.Println(jsonc.Array(1, 2).Keys())  // non-Object prints as []
}
Output:
[beta alpha gamma]
[]
[]
[]

func (*Node) Len

func (n *Node) Len() int

Len returns the number of members in an Object. Returns 0 for nil, non-Object, or empty objects.

Example

Len: count members in an Object.

package main

import (
	"fmt"

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

func main() {
	empty := jsonc.Object()
	single := jsonc.Object("x", 1)
	multi := jsonc.Object("a", 1, "b", 2, "c", 3)
	nested := jsonc.Object("inner", jsonc.Object("y", 2))
	fmt.Println(empty.Len())
	fmt.Println(single.Len())
	fmt.Println(multi.Len())
	fmt.Println(nested.Len())
	fmt.Println((*jsonc.Node)(nil).Len())
	fmt.Println(jsonc.Array(1, 2).Len())
}
Output:
0
1
3
1
0
0

func (*Node) MarshalPath added in v0.2.0

func (n *Node) MarshalPath(path string, v any) error

MarshalPath serializes a Go struct v to a CST subtree and replaces the member at path.

doc.Root().MarshalPath("xkeen.speed_balancer", sbSettings)

Struct fields are mapped by json tag; the optional jsonc tag provides a line comment that precedes the member. Example:

type Config struct {
    Enabled bool   `json:"enabled" jsonc:"Enable the feature"`
    Name    string `json:"name"`
}

Path "" means replace this node itself (it must be an Object).

func (*Node) Members

func (n *Node) Members() []*Node

Members returns the member nodes of an Object, or nil.

func (*Node) RawText

func (n *Node) RawText() string

RawText reconstructs the original source text for this node. For leaf nodes, returns Value directly. For container nodes, concatenates children's RawText.

func (*Node) Root added in v0.2.0

func (n *Node) Root() *Node

Root returns the first non-trivia child of a Document, typically an Object or Array — the root value of the parsed JSONC document. Returns nil for nil, non-Document, or empty documents.

func (*Node) Set

func (n *Node) Set(key string, value any, comments ...string) *Node

Set adds or updates a member on an Object node.

  • If key already exists, its value (and optional comment) is replaced.
  • If key does not exist, a new member is appended.
  • comments (optional) are added as trailing line comments after the value.

Returns the receiver for fluent chaining:

obj := jsonc.Object("a", 1).Set("b", 2).Set("c", 3)
Example (Chain)

Setter chain: fluent interface.

package main

import (
	"fmt"

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

func main() {
	doc := jsonc.Object("a", 1).
		Set("b", 2).
		Set("c", 3)
	fmt.Print(jsonc.Format(doc, &jsonc.FormatOptions{Indent: ""}))
}
Output:
{
"a": 1,
"b": 2,
"c": 3
}
Example (Comment)

Setter with comment: trailing line comment.

package main

import (
	"fmt"
	"strings"

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

func main() {
	doc := jsonc.Object("host", "localhost")
	doc.Set("port", 8080, "default port")
	out := jsonc.Format(doc, &jsonc.FormatOptions{Indent: "  "})
	fmt.Print("comment count: ", strings.Count(out, "//"), "\n")
	fmt.Print("port value: ", strings.Contains(out, "8080"), "\n")
}
Output:
comment count: 1
port value: true
Example (Simple)

Setter: add a member to an existing object.

package main

import (
	"fmt"

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

func main() {
	doc := jsonc.Object("name", "Alice")
	doc.Set("age", 30)
	fmt.Print(jsonc.Format(doc, &jsonc.FormatOptions{Indent: "  "}))
}
Output:
{
  "name": "Alice",
  "age": 30
}
Example (Update)

Setter: update an existing key's value.

package main

import (
	"fmt"

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

func main() {
	doc := jsonc.Object("mode", "strict")
	doc.Set("mode", "lax")
	fmt.Print(jsonc.Format(doc, &jsonc.FormatOptions{Indent: ""}))
}
Output:
{
"mode": "lax"
}

func (*Node) SetCommentBody

func (n *Node) SetCommentBody(body string)

SetCommentBody updates the body of a comment node and reconstructs its raw text (Value). Leading/trailing whitespace is trimmed.

func (*Node) SetPath added in v0.2.0

func (n *Node) SetPath(path string, value any) *Node

SetPath sets a value at a dot-separated path, creating intermediate Objects as needed (auto-vivify).

obj.SetPath("xkeen.speed_balancer.enabled", true)
arr.SetPath("0", "replaced")   // replace first array element

Numeric intermediate segments index into Arrays; non-numeric segments access Object keys. Auto-vivify creates Objects for missing keys. Returns the receiver for fluent chaining.

func (*Node) SetValue

func (n *Node) SetValue(text string)

SetValue sets a leaf node's text value and updates the End position.

Example
package main

import (
	"fmt"

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

func main() {
	src := `{"name": "Alice"}`
	doc, _ := jsonc.Parse(src)
	obj := doc.FirstChild()
	obj.Members()[0].ValueNode().SetValue(`"Bob"`)
	fmt.Println(jsonc.Serialize(doc))
}
Output:
{"name": "Bob"}

func (*Node) String

func (n *Node) String() string

String returns a human-readable debug representation of the node tree.

func (*Node) UnmarshalPath added in v0.2.0

func (n *Node) UnmarshalPath(path string, v any) error

UnmarshalPath navigates to the subtree at path, serializes it to plain JSON (stripping comments), and unmarshals into v using encoding/json.

v := SpeedBalancerSettings{}
err := doc.Root().UnmarshalPath("xkeen.speed_balancer", &v)

path "" means unmarshal from this node itself.

func (*Node) ValueNode

func (n *Node) ValueNode() *Node

ValueNode returns the value child of a Member node, or nil. The value is the node after the colon — this skips the key (also a KindString).

func (*Node) Values

func (n *Node) Values() []*Node

Values returns the value nodes of an Object in document order. Returns nil for nil, non-Object, or empty objects.

Example

Values: list value nodes in document order.

package main

import (
	"fmt"

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

func main() {
	doc := jsonc.Object("a", 10, "b", 20)
	for _, v := range doc.Values() {
		fmt.Println(v.Value)
	}
	fmt.Println(jsonc.Object().Values())
}
Output:
10
20
[]

func (*Node) Walk

func (n *Node) Walk(fn func(*Node) bool)

Walk traverses the tree depth-first, calling fn for every node. If fn returns false, the walk stops descending into that node's children.

Example
package main

import (
	"fmt"
	"log"

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

func main() {
	src := `[true, false, null]`
	doc, err := jsonc.Parse(src)
	if err != nil {
		log.Fatal(err)
	}
	var kinds []string
	doc.Walk(func(n *jsonc.Node) bool {
		if n.IsValue() {
			kinds = append(kinds, n.Kind.String())
		}
		return true
	})
	fmt.Println(kinds)
}
Output:
[Array Boolean Boolean Null]

func (*Node) WriteFile added in v0.2.0

func (n *Node) WriteFile(path string) error

WriteFile serializes a Document and writes it to a file.

type NodeKind

type NodeKind int

NodeKind identifies the type of a CST node.

const (
	KindDocument NodeKind = iota
	KindObject
	KindArray
	KindMember // "key": value pair
	KindString
	KindNumber
	KindBoolean
	KindNull
	KindComment // line or block comment
	KindWhitespace
	KindComma
	KindColon
	KindLBrace   // {
	KindRBrace   // }
	KindLBracket // [
	KindRBracket // ]
	KindEOF      // end of input
	KindError    // parse error / unexpected token
)

Node kinds.

func (NodeKind) String

func (k NodeKind) String() string

type Position

type Position struct {
	Offset int // 0-based byte offset from start of source
	Line   int // 0-based line number
	Column int // 0-based column (byte offset within the line)
}

Position represents a byte-offset position in source text.

func (Position) String

func (p Position) String() string

String formats a Position for display.

Jump to

Keyboard shortcuts

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