Documentation
¶
Overview ¶
Package jsonc provides a concrete syntax tree (CST) parser, serializer, and formatter for JSONC (JSON with Comments).
Index ¶
- func Format(doc *Node, opts *FormatOptions) string
- func Serialize(doc *Node) string
- type CommentStyle
- type FormatOptions
- type Node
- func Array(values ...any) *Node
- func NewArray(elements ...*Node) *Node
- func NewBoolean(val bool) *Node
- func NewCommentBlock(body string) *Node
- func NewCommentLine(body string) *Node
- func NewMember(key interface{}, val *Node, extra ...*Node) *Node
- func NewNull() *Node
- func NewNumber(value string) *Node
- func NewObject(items ...*Node) *Node
- func NewString(value string) *Node
- func Object(keyvals ...any) *Node
- func ObjectFromMembers(members []*Node) *Node
- func Parse(input string) (*Node, error)
- func ParseFile(path string) (*Node, error)
- func (n *Node) AppendChild(child *Node)
- func (n *Node) Body() string
- func (n *Node) DeepEqual(other *Node) bool
- func (n *Node) Delete(key string) *Node
- func (n *Node) DeletePath(path string) *Node
- func (n *Node) Elements() []*Node
- func (n *Node) FindAll(kind NodeKind) []*Node
- func (n *Node) FirstChild() *Node
- func (n *Node) FirstChildOfKind(kinds ...NodeKind) *Node
- func (n *Node) Get(key string) *Node
- func (n *Node) GetPath(path string) *Node
- func (n *Node) Has(key string) bool
- func (n *Node) IsContainer() bool
- func (n *Node) IsTrivia() bool
- func (n *Node) IsValue() bool
- func (n *Node) KeyNode() *Node
- func (n *Node) Keys() []string
- func (n *Node) Len() int
- func (n *Node) MarshalPath(path string, v any) error
- func (n *Node) Members() []*Node
- func (n *Node) RawText() string
- func (n *Node) Root() *Node
- func (n *Node) Set(key string, value any, comments ...string) *Node
- func (n *Node) SetCommentBody(body string)
- func (n *Node) SetPath(path string, value any) *Node
- func (n *Node) SetValue(text string)
- func (n *Node) String() string
- func (n *Node) UnmarshalPath(path string, v any) error
- func (n *Node) ValueNode() *Node
- func (n *Node) Values() []*Node
- func (n *Node) Walk(fn func(*Node) bool)
- func (n *Node) WriteFile(path string) error
- type NodeKind
- type Position
Examples ¶
- Array (Mixed)
- Format
- NewArray
- NewCommentBlock (Builder)
- NewCommentLine (Builder)
- NewObject
- Node.Body
- Node.Delete
- Node.Delete (Chain)
- Node.Get
- Node.Has
- Node.Keys
- Node.Len
- Node.Set (Chain)
- Node.Set (Comment)
- Node.Set (Simple)
- Node.Set (Update)
- Node.SetValue
- Node.Values
- Node.Walk
- Object
- Object (Nested)
- Parse
- Parse (Jsonc)
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 }
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 ¶
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 ¶
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 NewCommentBlock ¶
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 ¶
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 ¶
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 NewObject ¶
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 ¶
NewString creates a new JSON string value node. The value is the raw JSON literal including surrounding quotes.
func Object ¶
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
ObjectFromMembers creates an Object CST node from a slice of Member nodes.
func Parse ¶
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 (*Node) AppendChild ¶
AppendChild appends a child node and updates Start/End positions.
func (*Node) Body ¶
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 ¶
DeepEqual checks structural equality: same kind, same value, same children.
func (*Node) Delete ¶
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
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) FirstChild ¶
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 ¶
FirstChildOfKind returns the first child matching any of the given kinds.
func (*Node) Get ¶
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
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 ¶
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 ¶
IsContainer returns true for nodes that can have child value nodes.
func (*Node) Keys ¶
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 ¶
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
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) RawText ¶
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
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 ¶
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 ¶
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
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 ¶
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) UnmarshalPath ¶ added in v0.2.0
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 ¶
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 ¶
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 ¶
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]
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.