Low-level AST (Abstract Syntax Tree) manipulation for fine-grained control over YAML document structure, including comments, anchors, aliases, and formatting. Use Node when you need to preserve or manipulate YAML structure beyond what basic marshaling/unmarshaling provides.
Represents an element in the YAML document hierarchy. Provides intermediate representation that allows detailed control over content being decoded or encoded.
type Node struct {
Kind Kind
Style Style
Tag string
Value string
Anchor string
Alias *Node
Content []*Node
HeadComment string
LineComment string
FootComment string
Line int
Column int
}Defines whether the node is a document, mapping, sequence, scalar value, or alias to another node. See Kind type below.
Allows customizing the appearance of the node in the tree (quoted strings, flow style, etc.). See Style type below.
YAML tag defining the data type for the value. When decoding, this field is always set to the resolved tag even if not explicitly provided. When encoding, the tag is serialized only if TaggedStyle is used or the implicit tag diverges from the provided one.
Unescaped and unquoted representation of the value (for scalar nodes only).
Anchor name for this node, which allows aliases to point to it.
The node that this alias points to. Only valid when Kind is AliasNode.
Contains child nodes for documents, mappings, and sequences. For mappings, nodes alternate between keys and values.
Comments in the lines preceding the node, not separated by an empty line.
Comments at the end of the line where the node is located.
Comments following the node and before empty lines.
Line number in the decoded YAML text where the node appears. Not respected when encoding.
Column number in the decoded YAML text where the node appears. Not respected when encoding.
Decodes the node and stores its data into the value pointed to by v.
func (n *Node) Decode(v interface{}) (err error)v (interface{}) - Pointer to the variable where decoded values will be storederr (error) - Returns *yaml.TypeError if type mismatches occur, nil on successSee Basic YAML Operations for details on type conversion.
Encodes value v and stores its representation in the node.
func (n *Node) Encode(v interface{}) (err error)v (interface{}) - The value to encode into the nodeerr (error) - Error if encoding fails, nil otherwiseReturns whether the node has all of its fields unset.
func (n *Node) IsZero() boolbool - True if all fields are unset, false otherwiseReturns the long form of the tag that indicates the data type for the node.
func (n *Node) LongTag() stringstring - Long form tag (e.g., "tag:yaml.org,2002:str")If the Tag field isn't explicitly defined, one will be computed based on the node properties.
Returns the short form of the YAML tag that indicates data type for the node.
func (n *Node) ShortTag() stringstring - Short form tag (e.g., "!!str")If the Tag field isn't explicitly defined, one will be computed based on the node properties.
Convenience function that sets the node to a string value and defines its style appropriately.
func (n *Node) SetString(s string)s (string) - The string value to setKind to ScalarNodeTag to strTagTag to binaryTagStyle to LiteralStyleRepresents the type of YAML node.
type Kind uint32
const (
DocumentNode Kind = 1 << iota
SequenceNode
MappingNode
ScalarNode
AliasNode
)Controls the rendering style of YAML nodes.
type Style uint32
const (
TaggedStyle Style = 1 << iota
DoubleQuotedStyle
SingleQuotedStyle
LiteralStyle
FoldedStyle
FlowStyle
)|)>)package main
import (
"fmt"
"gopkg.in/yaml.v3"
)
func main() {
data := []byte(`
# This is a head comment
name: MyApp # This is a line comment
version: 1
# This is a foot comment
`)
var node yaml.Node
err := yaml.Unmarshal(data, &node)
if err != nil {
panic(err)
}
// Navigate to the document content (root mapping)
doc := node.Content[0]
// Iterate through mapping key-value pairs
for i := 0; i < len(doc.Content); i += 2 {
key := doc.Content[i]
value := doc.Content[i+1]
fmt.Printf("Key: %s\n", key.Value)
fmt.Printf("Value: %s\n", value.Value)
fmt.Printf("HeadComment: %q\n", key.HeadComment)
fmt.Printf("LineComment: %q\n", value.LineComment)
fmt.Printf("FootComment: %q\n", value.FootComment)
}
}func AddField(data []byte, key, value string) ([]byte, error) {
var node yaml.Node
err := yaml.Unmarshal(data, &node)
if err != nil {
return nil, err
}
// Get the root mapping
root := node.Content[0]
// Create key node
keyNode := &yaml.Node{
Kind: yaml.ScalarNode,
Value: key,
}
// Create value node
valueNode := &yaml.Node{
Kind: yaml.ScalarNode,
Value: value,
}
// Add to content
root.Content = append(root.Content, keyNode, valueNode)
// Marshal back to YAML
return yaml.Marshal(&node)
}func CreateFlowStyleYAML() ([]byte, error) {
// Create a mapping node with flow style
node := yaml.Node{
Kind: yaml.DocumentNode,
Content: []*yaml.Node{
{
Kind: yaml.MappingNode,
Style: yaml.FlowStyle,
Content: []*yaml.Node{
{Kind: yaml.ScalarNode, Value: "name"},
{Kind: yaml.ScalarNode, Value: "MyApp"},
{Kind: yaml.ScalarNode, Value: "items"},
{
Kind: yaml.SequenceNode,
Style: yaml.FlowStyle,
Content: []*yaml.Node{
{Kind: yaml.ScalarNode, Value: "item1"},
{Kind: yaml.ScalarNode, Value: "item2"},
{Kind: yaml.ScalarNode, Value: "item3"},
},
},
},
},
},
}
return yaml.Marshal(&node)
// Output: {name: MyApp, items: [item1, item2, item3]}
}func AddCommentToField(data []byte, fieldName, comment string) ([]byte, error) {
var node yaml.Node
err := yaml.Unmarshal(data, &node)
if err != nil {
return nil, err
}
// Get the root mapping
root := node.Content[0]
// Find the field and add comment
for i := 0; i < len(root.Content); i += 2 {
key := root.Content[i]
value := root.Content[i+1]
if key.Value == fieldName {
value.LineComment = comment
break
}
}
return yaml.Marshal(&node)
}
// Usage:
data := []byte("name: MyApp\nversion: 1")
result, _ := AddCommentToField(data, "version", "Major version")
fmt.Printf("%s", result)
// Output:
// name: MyApp
// version: 1 # Major versionfunc CreateYAMLWithAnchors() ([]byte, error) {
// Create a node with an anchor
defaultConfig := &yaml.Node{
Kind: yaml.MappingNode,
Anchor: "default",
Content: []*yaml.Node{
{Kind: yaml.ScalarNode, Value: "timeout"},
{Kind: yaml.ScalarNode, Value: "30"},
{Kind: yaml.ScalarNode, Value: "retries"},
{Kind: yaml.ScalarNode, Value: "3"},
},
}
// Create an alias to the anchor
alias := &yaml.Node{
Kind: yaml.AliasNode,
Alias: defaultConfig,
}
// Build document
doc := yaml.Node{
Kind: yaml.DocumentNode,
Content: []*yaml.Node{
{
Kind: yaml.MappingNode,
Content: []*yaml.Node{
{Kind: yaml.ScalarNode, Value: "default"},
defaultConfig,
{Kind: yaml.ScalarNode, Value: "production"},
alias,
},
},
},
}
return yaml.Marshal(&doc)
// Output:
// default: &default
// timeout: 30
// retries: 3
// production: *default
}You can unmarshal directly into a Node to work with the AST:
package main
import (
"fmt"
"gopkg.in/yaml.v3"
)
func main() {
data := []byte(`
name: MyApp
version: 1
config:
debug: true
port: 8080
`)
var node yaml.Node
err := yaml.Unmarshal(data, &node)
if err != nil {
panic(err)
}
// node.Kind == DocumentNode
// node.Content[0].Kind == MappingNode
// Navigate the tree structure
walkNode(&node, 0)
}
func walkNode(node *yaml.Node, depth int) {
indent := ""
for i := 0; i < depth; i++ {
indent += " "
}
fmt.Printf("%sKind: %d, Value: %q\n", indent, node.Kind, node.Value)
for _, child := range node.Content {
walkNode(child, depth+1)
}
}You can embed Node fields within regular structs:
type Config struct {
Name string
Version int
// Store the raw YAML node for the metadata section
Metadata yaml.Node `yaml:"metadata"`
}
data := []byte(`
name: MyApp
version: 1
metadata:
author: Alice
tags:
- production
- stable
`)
var config Config
yaml.Unmarshal(data, &config)
// Access regular fields
fmt.Println(config.Name) // "MyApp"
fmt.Println(config.Version) // 1
// Work with the metadata node
var meta map[string]interface{}
config.Metadata.Decode(&meta)
fmt.Println(meta) // map[author:Alice tags:[production stable]]func MergeYAML(base, override []byte) ([]byte, error) {
var baseNode, overrideNode yaml.Node
if err := yaml.Unmarshal(base, &baseNode); err != nil {
return nil, err
}
if err := yaml.Unmarshal(override, &overrideNode); err != nil {
return nil, err
}
// Merge logic: add override fields to base
baseRoot := baseNode.Content[0]
overrideRoot := overrideNode.Content[0]
baseMap := make(map[string]int)
for i := 0; i < len(baseRoot.Content); i += 2 {
key := baseRoot.Content[i].Value
baseMap[key] = i
}
for i := 0; i < len(overrideRoot.Content); i += 2 {
key := overrideRoot.Content[i]
value := overrideRoot.Content[i+1]
if idx, exists := baseMap[key.Value]; exists {
// Replace existing value
baseRoot.Content[idx+1] = value
} else {
// Add new key-value pair
baseRoot.Content = append(baseRoot.Content, key, value)
}
}
return yaml.Marshal(&baseNode)
}func RemoveFields(data []byte, fieldsToRemove []string) ([]byte, error) {
var node yaml.Node
if err := yaml.Unmarshal(data, &node); err != nil {
return nil, err
}
root := node.Content[0]
removeSet := make(map[string]bool)
for _, f := range fieldsToRemove {
removeSet[f] = true
}
newContent := []*yaml.Node{}
for i := 0; i < len(root.Content); i += 2 {
key := root.Content[i]
value := root.Content[i+1]
if !removeSet[key.Value] {
newContent = append(newContent, key, value)
}
}
root.Content = newContent
return yaml.Marshal(&node)
}Line and Column fields are populated during unmarshaling but are not used during marshalingNode and re-marshaling, though exact formatting may changeContent alternates between key and value nodes. For sequences, each element is a separate node.