yaml

package module
v1.1.0 Latest Latest
Warning

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

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

README

go-rotini/yaml

A Go YAML encoding and decoding package that implements the YAML 1.2.2 specification backed by the YAML Test Suite conformance tests.

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

Features

  • Full YAML 1.2.2 specification support with Core, JSON, and Failsafe schema resolution
  • Tested against the official YAML Test Suite for conformance
  • Generic UnmarshalTo[T] API and type-safe custom marshaler/unmarshaler registration
  • Multi-document streaming with Encoder/Decoder
  • Struct field tags: omitempty, flow, inline, required
  • Encode options: indent, flow style, literal blocks, single quotes, JSON-compatible output, comments
  • Decode options: strict mode, duplicate key rejection, ordered maps, custom tag resolvers, struct validation
  • Anchor/alias resolution with cycle detection and merge key (<<) support
  • Cross-file anchor references via WithReferenceFiles/WithReferenceDirs
  • AST access via Parse, Walk, Filter, and Node tree manipulation
  • JSONPath-like query engine (PathString) with read, replace, append, and delete operations
  • Bidirectional JSON conversion (ToJSON/FromJSON) and WithJSONUnmarshaler fallback
  • Valid function for quick syntax validation without full decoding
  • FormatError for human-readable error output with source line and column pointer
  • Context-aware encoding/decoding via EncodeContext/DecodeContext
  • UTF-8, UTF-16 (LE/BE), and UTF-32 (LE/BE) encoding detection
  • DoS protection: exponential alias expansion (billion laughs), quadratic blowup, deep nesting stack exhaustion, and oversized document attacks

Installation

go get github.com/go-rotini/yaml

Requires Go 1.26 or later.

Quick Start

package main

import (
	"fmt"
	"log"

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

type Service struct {
	Host    string   `yaml:"host,required"`
	Port    int      `yaml:"port"`
	Debug   bool     `yaml:"debug,omitempty"`
	Tags    []string `yaml:"tags,flow"`
	Token   string   `yaml:"-"`
}

func main() {
	// Marshal
	s := Service{Host: "localhost", Port: 8080, Tags: []string{"v1", "v2"}, Token: "hidden"}
	b, err := yaml.Marshal(s)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(b))

	// Unmarshal
	var s1 Service
	if err := yaml.Unmarshal(b, &s1); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%+v\n", s1)

	// Generic unmarshal (no pointer required)
	s2, err := yaml.UnmarshalTo[Service](b)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%+v\n", s2)
}

Documentation

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

Contributing

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

Code of Conduct

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

Security

To report a vulnerability, see SECURITY.md.

License

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

Documentation

Overview

Package yaml implements YAML 1.2.2 encoding and decoding.

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 YAML keys and Go fields.

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

Struct Tags

Struct fields may be annotated with "yaml" tags:

type Config struct {
    Name    string `yaml:"name"`
    Count   int    `yaml:"count,omitempty"`
    Ignored string `yaml:"-"`
}

The tag format is "keyname,opts" where opts is a comma-separated list of:

  • omitempty: omit the field if it has its zero value
  • inline: inline the struct's fields into the parent mapping
  • flow: encode the field in flow style (e.g. [a, b] or {k: v})
  • required: return an error during decoding if the key is absent
  • default=<value>: set field to <value> during decoding if the key is absent (requires WithDefaults; scalar types only)

A tag of "-" excludes the field from encoding and decoding.

Custom Marshalers

Types can implement Marshaler, BytesMarshaler, MarshalerContext, Unmarshaler, BytesUnmarshaler, or UnmarshalerContext for custom serialization logic.

Error Handling

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

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

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

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrSyntax       = &SyntaxError{}
	ErrType         = &TypeError{}
	ErrUnknownField = &UnknownFieldError{}
	ErrCycle        = &CycleError{}
	ErrDuplicateKey = &DuplicateKeyError{}
	ErrValidation   = &ValidationError{}
	ErrDefault      = &DefaultError{}

	ErrPathSyntax   = errors.New("yaml: invalid path syntax")
	ErrPathNotFound = errors.New("yaml: path not found")
	ErrNilPointer   = errors.New("yaml: non-nil pointer required")
	ErrDocumentSize = errors.New("yaml: document size exceeds limit")
	ErrPathEscape   = errors.New("yaml: reference path escapes allowed directory")
)

Sentinel errors for use with errors.Is.

Functions

func FormatError

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

FormatError returns a human-readable string for a SyntaxError or ValidationError that 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.

Example
package main

import (
	"fmt"

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

func main() {
	data := []byte("key: [bad\n")
	var v any
	err := yaml.Unmarshal(data, &v)
	if err != nil {
		formatted := yaml.FormatError(data, err)
		fmt.Print(formatted)
	}
}

func FromJSON

func FromJSON(jsonData []byte) ([]byte, error)

FromJSON converts JSON bytes to YAML bytes. The JSON input is decoded into an untyped value and then re-encoded as YAML.

Example
package main

import (
	"fmt"

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

func main() {
	data, err := yaml.FromJSON([]byte(`{"name":"test","port":8080}`))
	if err != nil {
		panic(err)
	}
	fmt.Print(string(data))
}
Output:
name: test
port: 8080

func Marshal

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

Marshal serializes v into YAML bytes using default encoding options.

Example
package main

import (
	"fmt"

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

func main() {
	type Server struct {
		Host string `yaml:"host"`
		Port int    `yaml:"port"`
	}
	v := Server{Host: "localhost", Port: 8080}
	data, err := yaml.Marshal(v)
	if err != nil {
		panic(err)
	}
	fmt.Print(string(data))
}
Output:
host: localhost
port: 8080

func MarshalWithOptions

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

MarshalWithOptions serializes v into YAML bytes, applying the given EncodeOption values to control formatting and style.

Example
package main

import (
	"fmt"

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

func main() {
	v := map[string][]int{"nums": {1, 2, 3}}
	data, err := yaml.MarshalWithOptions(v, yaml.WithFlow(true))
	if err != nil {
		panic(err)
	}
	fmt.Print(string(data))
}
Output:
{nums: [1, 2, 3]}

func NodeToBytes

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

NodeToBytes serializes a Node tree back into YAML bytes using default encoding options.

Example
package main

import (
	"fmt"

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

func main() {
	file, _ := yaml.Parse([]byte("name: hello\n"))
	out, err := yaml.NodeToBytes(file.Docs[0])
	if err != nil {
		panic(err)
	}
	fmt.Print(string(out))
}
Output:
name: hello

func NodeToBytesWithOptions

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

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

Example
package main

import (
	"fmt"

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

func main() {
	doc := &yaml.Node{
		Kind: yaml.DocumentNode,
		Children: []*yaml.Node{{
			Kind: yaml.MappingNode,
			Children: []*yaml.Node{
				{Kind: yaml.ScalarNode, Value: "name"},
				{Kind: yaml.ScalarNode, Value: "hello"},
			},
		}},
	}
	out, err := yaml.NodeToBytesWithOptions(doc, yaml.WithSingleQuote(true))
	if err != nil {
		panic(err)
	}
	fmt.Print(string(out))
}
Output:
name: hello

func ToJSON

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

ToJSON converts YAML bytes to JSON bytes. The YAML input is decoded into an untyped value and then re-encoded as JSON.

Example
package main

import (
	"fmt"

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

func main() {
	data, err := yaml.ToJSON([]byte("name: test\ncount: 42\n"))
	if err != nil {
		panic(err)
	}
	fmt.Println(string(data))
}
Output:
{"count":42,"name":"test"}

func Unmarshal

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

Unmarshal parses YAML data and stores the first document into the value pointed to by v. If v is nil or not a pointer, Unmarshal returns an error.

Example
package main

import (
	"fmt"

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

func main() {
	data := []byte("name: app\nport: 3000\n")
	var v map[string]any
	if err := yaml.Unmarshal(data, &v); err != nil {
		panic(err)
	}
	fmt.Println(v["name"])
	fmt.Println(v["port"])
}
Output:
app
3000

func UnmarshalTo

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

UnmarshalTo parses YAML data into a value of type T and returns it. This is a generic alternative to Unmarshal that allocates the target value internally, removing the need for the caller to declare a variable and pass its address.

Example
package main

import (
	"fmt"

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

func main() {
	type Config struct {
		Name string `yaml:"name"`
		Port int    `yaml:"port"`
	}
	cfg, err := yaml.UnmarshalTo[Config]([]byte("name: api\nport: 9090\n"))
	if err != nil {
		panic(err)
	}
	fmt.Println(cfg.Name, cfg.Port)
}
Output:
api 9090

func UnmarshalWithOptions

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

UnmarshalWithOptions parses YAML data into v, applying the given DecodeOption values to control strictness, limits, and custom resolvers.

Example
package main

import (
	"fmt"

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

func main() {
	data := []byte("name: test\n")
	var v map[string]any
	if err := yaml.UnmarshalWithOptions(data, &v, yaml.WithSchema(yaml.FailsafeSchema)); err != nil {
		panic(err)
	}
	fmt.Println(v["name"])
}
Output:
test
Example (Strict)
package main

import (
	"fmt"

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

func main() {
	type Config struct {
		Name string `yaml:"name"`
	}
	data := []byte("name: test\nunknown: field\n")
	var cfg Config
	err := yaml.UnmarshalWithOptions(data, &cfg, yaml.WithStrict())
	fmt.Println(err != nil)
}
Output:
true

func Valid

func Valid(data []byte) bool

Valid reports whether data is valid YAML.

Example
package main

import (
	"fmt"

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

func main() {
	fmt.Println(yaml.Valid([]byte("key: value")))
	fmt.Println(yaml.Valid([]byte("key: [invalid")))
}
Output:
true
false

func Walk

func Walk(n *Node, fn WalkFunc)

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

Example
package main

import (
	"fmt"

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

func main() {
	file, _ := yaml.Parse([]byte("a: 1\nb: 2\n"))
	var keys []string
	yaml.Walk(file.Docs[0], func(n *yaml.Node) bool {
		if n.Kind == yaml.MappingNode {
			for i := 0; i < len(n.Children)-1; i += 2 {
				keys = append(keys, n.Children[i].Value)
			}
		}
		return true
	})
	fmt.Println(keys)
}
Output:
[a b]

Types

type BytesMarshaler

type BytesMarshaler interface {
	MarshalYAML() ([]byte, error)
}

BytesMarshaler is implemented by types that can encode themselves directly into YAML bytes.

type BytesUnmarshaler

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

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

type Comment

type Comment struct {
	Position CommentPosition
	Text     string
}

Comment attaches a comment to a YAML 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
)

type CycleError

type CycleError struct {
	Anchor string
	Pos    Position
}

CycleError is returned when alias expansion detects a cycle or exceeds the maximum expansion depth set by WithMaxAliasExpansion.

func (*CycleError) Error

func (e *CycleError) Error() string

func (*CycleError) Is

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

type DecodeOption

type DecodeOption func(*decoderOptions)

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

func WithCustomUnmarshaler

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

WithCustomUnmarshaler registers a function that decodes YAML bytes into a value of type T, overriding the default decoding for that type.

Example
package main

import (
	"fmt"
	"strings"

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

func main() {
	type IP [4]byte
	var v struct {
		Addr IP `yaml:"addr"`
	}
	err := yaml.UnmarshalWithOptions([]byte("addr: 10.0.0.1\n"), &v,
		yaml.WithCustomUnmarshaler(func(ip *IP, data []byte) error {
			s := strings.TrimSpace(string(data))
			var a, b, c, d byte
			fmt.Sscanf(s, "%d.%d.%d.%d", &a, &b, &c, &d)
			*ip = IP{a, b, c, d}
			return nil
		}),
	)
	if err != nil {
		panic(err)
	}
	fmt.Println(v.Addr)
}
Output:
[10 0 0 1]

func WithDefaults added in v1.0.5

func WithDefaults() DecodeOption

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

func WithDisallowDuplicateKey

func WithDisallowDuplicateKey() DecodeOption

WithDisallowDuplicateKey causes decoding to return a DuplicateKeyError if a mapping contains the same key more than once.

Example
package main

import (
	"errors"
	"fmt"

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

func main() {
	var v map[string]int
	err := yaml.UnmarshalWithOptions([]byte("a: 1\na: 2\n"), &v, yaml.WithDisallowDuplicateKey())
	fmt.Println(errors.Is(err, yaml.ErrDuplicateKey))
}
Output:
true

func WithJSONUnmarshaler

func WithJSONUnmarshaler() DecodeOption

WithJSONUnmarshaler causes the decoder to try a type's UnmarshalJSON method if no YAML-specific unmarshaler is found.

func WithMaxAliasExpansion

func WithMaxAliasExpansion(n int) DecodeOption

WithMaxAliasExpansion limits the total number of alias expansions during decoding (default 1000). This prevents denial-of-service via exponentially expanding aliases (the "billion laughs" attack).

Example
package main

import (
	"fmt"

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

func main() {
	data := []byte("a: &x [1,2]\nb: &y [*x,*x]\nc: &z [*y,*y]\nd: [*z,*z]\n")
	var v any
	err := yaml.UnmarshalWithOptions(data, &v, yaml.WithMaxAliasExpansion(1))
	fmt.Println(err != nil)
}
Output:
true

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.

Example
package main

import (
	"fmt"

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

func main() {
	type Inner struct {
		Value int `yaml:"value"`
	}
	type Outer struct {
		Inner Inner `yaml:"inner"`
	}
	var v Outer
	err := yaml.UnmarshalWithOptions([]byte("inner:\n  value: 1\n"), &v, yaml.WithMaxDepth(100))
	if err != nil {
		panic(err)
	}
	fmt.Println(v.Inner.Value)
}
Output:
1

func WithMaxDocumentSize

func WithMaxDocumentSize(n int) DecodeOption

WithMaxDocumentSize rejects input that exceeds n bytes before parsing begins.

Example
package main

import (
	"bytes"
	"errors"
	"fmt"

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

func main() {
	big := bytes.Repeat([]byte("a"), 1000)
	var v any
	err := yaml.UnmarshalWithOptions(big, &v, yaml.WithMaxDocumentSize(100))
	fmt.Println(errors.Is(err, yaml.ErrDocumentSize))
}
Output:
true

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 mappings instead of map[string]any, preserving key order.

Example
package main

import (
	"fmt"

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

func main() {
	var v any
	if err := yaml.UnmarshalWithOptions([]byte("b: 2\na: 1\n"), &v, yaml.WithOrderedMap()); err != nil {
		panic(err)
	}
	ms := v.(yaml.MapSlice)
	for _, item := range ms {
		fmt.Printf("%s=%v\n", item.Key, item.Value)
	}
}
Output:
b=2
a=1

func WithRecursiveDir

func WithRecursiveDir(b bool) DecodeOption

WithRecursiveDir controls whether WithReferenceDirs walks subdirectories recursively. Symlinks that escape the directory root are rejected.

func WithReferenceDirs

func WithReferenceDirs(dirs ...string) DecodeOption

WithReferenceDirs loads all .yaml and .yml files in the given directories and makes their anchors available for alias resolution. By default only the top level of each directory is scanned; use WithRecursiveDir to walk subdirectories.

func WithReferenceFiles

func WithReferenceFiles(files ...string) DecodeOption

WithReferenceFiles loads the given YAML files and makes their anchors available for alias resolution in the primary document.

func WithSchema

func WithSchema(s Schema) DecodeOption

WithSchema selects the YAML tag resolution schema. The default is CoreSchema. Use JSONSchema for JSON-compatible resolution or FailsafeSchema to treat all plain scalars as strings.

Example
package main

import (
	"fmt"

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

func main() {
	var v any
	if err := yaml.UnmarshalWithOptions([]byte("val: true\n"), &v, yaml.WithSchema(yaml.FailsafeSchema)); err != nil {
		panic(err)
	}
	m := v.(map[string]any)
	fmt.Printf("%T: %v\n", m["val"], m["val"])
}
Output:
string: true

func WithStrict

func WithStrict() DecodeOption

WithStrict causes decoding to return an UnknownFieldError if a YAML key does not correspond to any field in the target struct.

Example
package main

import (
	"errors"
	"fmt"

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

func main() {
	type Config struct {
		Name string `yaml:"name"`
	}
	var cfg Config
	err := yaml.UnmarshalWithOptions([]byte("name: ok\nextra: bad\n"), &cfg, yaml.WithStrict())
	fmt.Println(errors.Is(err, yaml.ErrUnknownField))
}
Output:
true

func WithTagResolver

func WithTagResolver(resolver *TagResolver) DecodeOption

WithTagResolver registers a TagResolver that handles a custom YAML tag (e.g. "!mytype") during decoding.

Example
package main

import (
	"fmt"
	"reflect"
	"strings"

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

func main() {
	resolver := &yaml.TagResolver{
		Tag:    "!upper",
		GoType: reflect.TypeOf(""),
		Resolve: func(value string) (any, error) {
			return strings.ToUpper(value), nil
		},
	}
	var v map[string]string
	err := yaml.UnmarshalWithOptions([]byte("name: !upper hello\n"), &v, yaml.WithTagResolver(resolver))
	if err != nil {
		panic(err)
	}
	fmt.Println(v["name"])
}
Output:
HELLO

func WithValidator

func WithValidator(v StructValidator) DecodeOption

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

Example
package main

import (
	"fmt"

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

func main() {
	type Config struct {
		Port int `yaml:"port"`
	}
	validator := validatorFunc(func(v any) error {
		if c, ok := v.(*Config); ok && c.Port < 1 {
			return fmt.Errorf("port must be positive")
		}
		return nil
	})
	var cfg Config
	err := yaml.UnmarshalWithOptions([]byte("port: 0\n"), &cfg, yaml.WithValidator(validator))
	fmt.Println(err != nil)
}

type validatorFunc func(any) error

func (f validatorFunc) Struct(v any) error { return f(v) }
Output:
true

type Decoder

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

Decoder reads and decodes YAML documents from an input stream. Use successive calls to Decoder.Decode to iterate over a multi-document stream; it returns io.EOF when no more documents remain.

The entire input is consumed from the reader on the first call to Decode. For streams where documents arrive incrementally (e.g. from a network connection), callers should frame each document and decode individually with Unmarshal.

A Decoder is not safe for concurrent use. Callers that need to decode from multiple goroutines must provide their own synchronization.

func NewDecoder

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

NewDecoder returns a new Decoder that reads from r.

Example
package main

import (
	"bytes"
	"fmt"
	"io"

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

func main() {
	data := "---\nhello: world\n---\nfoo: bar\n"
	dec := yaml.NewDecoder(bytes.NewReader([]byte(data)))
	for {
		var v map[string]string
		err := dec.Decode(&v)
		if err == io.EOF {
			break
		}
		if err != nil {
			panic(err)
		}
		fmt.Println(v)
	}
}
Output:
map[hello:world]
map[foo:bar]

func (*Decoder) Decode

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

Decode reads the next YAML document from the stream and stores it in the value pointed to by v. It returns io.EOF when the stream is exhausted.

func (*Decoder) DecodeContext

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

DecodeContext reads the next YAML document, passing ctx to types that implement UnmarshalerContext.

type DefaultError added in v1.0.5

type DefaultError struct {
	Field   string
	Message string
	Pos     Position
}

DefaultError is returned when a default value from a struct tag cannot be applied — for example, when the default string cannot be parsed into the target type, or when default is combined with required.

func (*DefaultError) Error added in v1.0.5

func (e *DefaultError) Error() string

func (*DefaultError) Is added in v1.0.5

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

type DuplicateKeyError

type DuplicateKeyError struct {
	Key string
	Pos Position
}

DuplicateKeyError is returned when decoding with WithDisallowDuplicateKey and a mapping contains the same key more than once.

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 WithAutoInt

func WithAutoInt(b bool) EncodeOption

WithAutoInt encodes float64 values that have no fractional part as integers (e.g. 42 instead of 42.0).

Example
package main

import (
	"fmt"

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

func main() {
	v := map[string]float64{"count": 42.0}
	data, _ := yaml.MarshalWithOptions(v, yaml.WithAutoInt(true))
	fmt.Print(string(data))
}
Output:
count: 42

func WithComment

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

WithComment attaches comments to nodes by dot-path key (e.g. "server.port"). Each key maps to a slice of Comment values specifying position and text.

Example
package main

import (
	"fmt"

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

func main() {
	v := map[string]int{"port": 8080}
	comments := map[string][]yaml.Comment{
		"port": {{Position: yaml.LineCommentPos, Text: "server port"}},
	}
	data, _ := yaml.MarshalWithOptions(v, yaml.WithComment(comments))
	fmt.Print(string(data))
}
Output:
port: 8080 # server port

func WithCustomMarshaler

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

WithCustomMarshaler registers a function that encodes values of type T to YAML bytes, overriding the default encoding for that type.

Example
package main

import (
	"fmt"

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

func main() {
	type Hex int
	data, _ := yaml.MarshalWithOptions(
		map[string]Hex{"code": 255},
		yaml.WithCustomMarshaler(func(h Hex) ([]byte, error) {
			return fmt.Appendf(nil, "0x%X", int(h)), nil
		}),
	)
	fmt.Print(string(data))
}
Output:
code: 0xFF

func WithFlow

func WithFlow(b bool) EncodeOption

WithFlow encodes all values in flow style (JSON-like inline notation) when set to true.

Example
package main

import (
	"fmt"

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

func main() {
	v := map[string][]string{"tags": {"go", "yaml"}}
	data, _ := yaml.MarshalWithOptions(v, yaml.WithFlow(true))
	fmt.Print(string(data))
}
Output:
{tags: [go, yaml]}

func WithIndent

func WithIndent(n int) EncodeOption

WithIndent sets the number of spaces per indentation level (default 2).

Example
package main

import (
	"fmt"

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

func main() {
	v := []string{"a", "b", "c"}
	data, _ := yaml.MarshalWithOptions(v, yaml.WithIndent(4))
	fmt.Print(string(data))
}
Output:
- a
- b
- c

func WithIndentSequence

func WithIndentSequence(b bool) EncodeOption

WithIndentSequence controls whether sequence items are indented relative to their parent key. When false (the default), the "- " prefix aligns with the parent's indentation level.

func WithJSON

func WithJSON(b bool) EncodeOption

WithJSON enables JSON-compatible output: all strings are double-quoted, keys are quoted, and null is used instead of YAML's tilde notation.

Example
package main

import (
	"fmt"

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

func main() {
	v := map[string]any{"name": "test", "active": true}
	data, _ := yaml.MarshalWithOptions(v, yaml.WithJSON(true))
	fmt.Print(string(data))
}
Output:
"active": true
"name": "test"

func WithLineWidth

func WithLineWidth(n int) EncodeOption

WithLineWidth sets the preferred line width for scalar wrapping (default 80). The encoder may exceed this width when a single word is longer than the limit.

func WithLiteralStyle

func WithLiteralStyle(b bool) EncodeOption

WithLiteralStyle encodes multi-line strings as YAML literal block scalars (|) instead of quoted scalars when set to true.

Example
package main

import (
	"fmt"

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

func main() {
	type Msg struct {
		Text string `yaml:"text"`
	}
	v := Msg{Text: "line1\nline2\n"}
	data, _ := yaml.MarshalWithOptions(v, yaml.WithLiteralStyle(true))
	fmt.Print(string(data))
}
Output:
text: |
    line1
    line2

func WithOmitEmpty

func WithOmitEmpty(b bool) EncodeOption

WithOmitEmpty omits struct fields and map entries whose values are zero/empty, equivalent to adding ",omitempty" to every field tag.

Example
package main

import (
	"fmt"

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

func main() {
	type Config struct {
		Name  string   `yaml:"name"`
		Tags  []string `yaml:"tags,omitempty"`
		Debug bool     `yaml:"debug,omitempty"`
	}
	data, _ := yaml.MarshalWithOptions(Config{Name: "app"}, yaml.WithOmitEmpty(true))
	fmt.Print(string(data))
}
Output:
name: app

func WithQuoteAllStrings

func WithQuoteAllStrings(b bool) EncodeOption

WithQuoteAllStrings forces all string scalar values to be quoted when set to true. Mapping keys remain unquoted unless they require quoting for syntactic reasons.

func WithSingleQuote

func WithSingleQuote(b bool) EncodeOption

WithSingleQuote prefers single-quoted scalars over double-quoted when the value contains no characters requiring escape sequences.

Example
package main

import (
	"fmt"

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

func main() {
	v := map[string]string{"name": "true"}
	data, _ := yaml.MarshalWithOptions(v, yaml.WithSingleQuote(true))
	fmt.Print(string(data))
}
Output:
name: 'true'

type Encoder

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

Encoder writes YAML values to an output stream. When multiple values are encoded, each is separated by a "---" document marker.

An Encoder is not safe for concurrent use. Callers that need to encode from multiple goroutines must provide their own synchronization.

func NewEncoder

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

NewEncoder returns a new Encoder that writes to w.

Example
package main

import (
	"bytes"
	"fmt"

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

func main() {
	var buf bytes.Buffer
	enc := yaml.NewEncoder(&buf)
	if err := enc.Encode(map[string]string{"a": "1"}); err != nil {
		panic(err)
	}
	if err := enc.Encode(map[string]string{"b": "2"}); err != nil {
		panic(err)
	}
	if err := enc.Close(); err != nil {
		panic(err)
	}
	fmt.Print(buf.String())
}
Output:
a: "1"
---
b: "2"

func (*Encoder) Close

func (enc *Encoder) Close() error

Close is a no-op. It is provided for symmetry with NewEncoder so callers can use a defer pattern without harm.

func (*Encoder) Encode

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

Encode writes the YAML encoding of v to the stream.

func (*Encoder) EncodeContext

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

EncodeContext writes the YAML encoding of v to the stream. The context is passed to types implementing MarshalerContext.

type File

type File struct {
	Docs     []*Node
	Warnings []string
}

File is the result of parsing a YAML byte stream. It contains one Node per document in the stream.

func Parse

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

Parse tokenizes and parses data into an AST. The returned File provides direct access to the document tree without decoding into Go values.

Example
package main

import (
	"fmt"

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

func main() {
	data := []byte("name: hello\nitems:\n  - a\n  - b\n")
	file, err := yaml.Parse(data)
	if err != nil {
		panic(err)
	}
	for _, doc := range file.Docs {
		yaml.Walk(doc, func(n *yaml.Node) bool {
			if n.Kind == yaml.ScalarNode {
				fmt.Println(n.Value)
			}
			return true
		})
	}
}
Output:
name
hello
items
a
b

type MapItem

type MapItem struct {
	Key   any
	Value any
}

MapItem is a single key-value pair within a MapSlice.

type MapSlice

type MapSlice []MapItem

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

type Marshaler

type Marshaler interface {
	MarshalYAML() (any, error)
}

Marshaler is implemented by types that can encode themselves into a YAML- compatible Go value. The returned value is then encoded normally.

type MarshalerContext

type MarshalerContext interface {
	MarshalYAML(ctx context.Context) (any, error)
}

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

type Node

type Node struct {
	Kind          NodeKind
	Tag           string
	Anchor        string
	Alias         string
	Value         string
	Style         ScalarStyle
	Flow          bool // flow style ({} or []) for mappings and sequences
	ExplicitStart bool // explicit document start marker (---)
	ExplicitEnd   bool // explicit document end marker (...)
	MergeKey      bool // node represents a merge key (<<)
	Children      []*Node
	Pos           Position
	Comment       string
	HeadComment   string
	FootComment   string
}

Node is a YAML AST node. For mappings, Children alternates key-value pairs (children[0] is the first key, children[1] its value, etc.). For sequences, each child is a list element. For documents, Children holds the root node.

func Filter

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

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

Example
package main

import (
	"fmt"

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

func main() {
	file, _ := yaml.Parse([]byte("a: 1\nb: hello\nc: 3\n"))
	scalars := yaml.Filter(file.Docs[0], func(n *yaml.Node) bool {
		return n.Kind == yaml.ScalarNode
	})
	for _, s := range scalars {
		fmt.Println(s.Value)
	}
}
Output:
a
1
b
hello
c
3

func (*Node) String

func (n *Node) String() string

String returns a short human-readable description of the node: the scalar value for scalars, a summary like "{mapping: 3 pairs}" for collections.

func (*Node) Validate

func (n *Node) Validate() error

Validate checks the structural consistency of the node tree rooted at n. It returns an error if a mapping has an odd number of children (missing value), or if an alias node references an empty name.

Example
package main

import (
	"fmt"

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

func main() {
	file, _ := yaml.Parse([]byte("a: 1\nb: 2\n"))
	err := file.Docs[0].Validate()
	fmt.Println(err)
}
Output:
<nil>

type NodeKind

type NodeKind int

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

const (
	DocumentNode NodeKind = iota // a YAML document (--- ... ---)
	MappingNode                  // a mapping (key: value pairs)
	SequenceNode                 // a sequence (- items)
	ScalarNode                   // a scalar value (string, number, bool, null)
	AliasNode                    // an alias reference (*name)
)

type Path

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

Path is a compiled JSONPath-like expression for querying and mutating a YAML Node tree. Create one with PathString.

func PathString

func PathString(expr string) (*Path, error)

PathString compiles a JSONPath-like expression into a Path.

Supported syntax:

  • $ — root node
  • .key — child mapping key
  • [n] — sequence index (negative indexes count from the end)
  • .* or [*] — wildcard (all children)
  • .. — recursive descent

Example: "$.servers[0].host" selects the host field of the first server.

Example
package main

import (
	"fmt"

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

func main() {
	path, err := yaml.PathString("$.name")
	if err != nil {
		panic(err)
	}
	data := []byte("name: world\nport: 80\n")
	result, err := path.ReadString(data)
	if err != nil {
		panic(err)
	}
	fmt.Println(result)
}
Output:
world

func (*Path) Append

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

Append adds value as a new child to each SequenceNode matched by the path within the tree rooted at n. Non-sequence matches are ignored.

Example
package main

import (
	"fmt"

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

func main() {
	file, _ := yaml.Parse([]byte("items:\n- a\n- b\n"))
	path, _ := yaml.PathString("$.items")
	newItem := &yaml.Node{Kind: yaml.ScalarNode, Value: "c"}
	if err := path.Append(file.Docs[0], newItem); err != nil {
		panic(err)
	}
	nodes, _ := path.Read(file.Docs[0])
	for _, child := range nodes[0].Children {
		fmt.Println(child.Value)
	}
}
Output:
a
b
c

func (*Path) Delete

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

Delete removes all nodes matching the path from the tree rooted at n. For mappings, both the key and value are removed. The path must have at least two segments (root + child).

Example
package main

import (
	"fmt"

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

func main() {
	file, _ := yaml.Parse([]byte("a: x\nb: y\nc: z\n"))
	path, _ := yaml.PathString("$.b")
	if err := path.Delete(file.Docs[0]); err != nil {
		panic(err)
	}
	out, _ := yaml.NodeToBytes(file.Docs[0])
	fmt.Print(string(out))
}
Output:
a: x
c: z

func (*Path) Read

func (p *Path) Read(n *Node) ([]*Node, error)

Read evaluates the path against the Node tree rooted at n and returns all matching nodes.

Example
package main

import (
	"fmt"

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

func main() {
	file, _ := yaml.Parse([]byte("servers:\n  - host: a\n  - host: b\n"))
	path, _ := yaml.PathString("$.servers[*].host")
	nodes, err := path.Read(file.Docs[0])
	if err != nil {
		panic(err)
	}
	for _, n := range nodes {
		fmt.Println(n.Value)
	}
}
Output:
a
b

func (*Path) ReadPositions

func (p *Path) ReadPositions(n *Node) ([]Position, error)

ReadPositions evaluates the path against the Node tree and returns the Position of every matching node.

Example
package main

import (
	"fmt"

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

func main() {
	file, _ := yaml.Parse([]byte("a: 1\nb: 2\nc: 3\n"))
	path, _ := yaml.PathString("$.b")
	positions, err := path.ReadPositions(file.Docs[0])
	if err != nil {
		panic(err)
	}
	for _, pos := range positions {
		fmt.Println(pos.Line)
	}
}
Output:
2

func (*Path) ReadString

func (p *Path) ReadString(data []byte) (string, error)

ReadString is a convenience that parses YAML data, evaluates the path against the first document, and returns the scalar Value of the first match.

Example
package main

import (
	"fmt"

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

func main() {
	path, _ := yaml.PathString("$.database.port")
	result, err := path.ReadString([]byte("database:\n  port: 5432\n"))
	if err != nil {
		panic(err)
	}
	fmt.Println(result)
}
Output:
5432

func (*Path) Replace

func (p *Path) Replace(n, replacement *Node) error

Replace finds all nodes matching the path within the tree rooted at n and replaces each with replacement. The path must have at least two segments (root + child).

Example
package main

import (
	"fmt"

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

func main() {
	file, _ := yaml.Parse([]byte("name: old\n"))
	path, _ := yaml.PathString("$.name")
	replacement := &yaml.Node{Kind: yaml.ScalarNode, Value: "new"}
	if err := path.Replace(file.Docs[0], replacement); err != nil {
		panic(err)
	}
	out, _ := yaml.NodeToBytes(file.Docs[0])
	fmt.Print(string(out))
}
Output:
name: new

func (*Path) String

func (p *Path) String() string

String returns the canonical string representation of the path expression.

type Position

type Position struct {
	Line   int
	Column int
	Offset int
}

Position identifies a location within YAML source text.

func (Position) String

func (p Position) String() string

type ScalarStyle

type ScalarStyle int

ScalarStyle indicates how a scalar was (or should be) represented in YAML.

const (
	PlainStyle        ScalarStyle = iota // unquoted (foo)
	SingleQuotedStyle                    // single-quoted ('foo')
	DoubleQuotedStyle                    // double-quoted ("foo")
	LiteralStyle                         // literal block (|)
	FoldedStyle                          // folded block (>)
)

type Schema

type Schema int

Schema selects the YAML tag resolution schema used when decoding plain scalars into interface{}/any values.

const (
	// CoreSchema resolves plain scalars using the YAML 1.2 Core schema:
	// null (~, null, Null, NULL, empty), bool (true/false in any case),
	// int (decimal, hex 0x, octal 0o), float (including .inf, .nan).
	CoreSchema Schema = iota

	// JSONSchema resolves plain scalars like JSON: null (lowercase only),
	// true/false (lowercase only), and JSON-format numbers.
	JSONSchema

	// FailsafeSchema treats all plain scalars as strings with no type
	// coercion. Only explicitly tagged values are resolved.
	FailsafeSchema
)

type StructValidator

type StructValidator interface {
	Struct(v any) error
}

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

type SyntaxError

type SyntaxError struct {
	Message string
	Pos     Position
	Token   string
}

SyntaxError is returned when the YAML 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 TagResolver

type TagResolver struct {
	Tag     string
	GoType  reflect.Type
	Resolve func(value string) (any, error)
}

TagResolver maps a custom YAML tag to a Go type and provides a function that converts the scalar string value into the target type. Register resolvers with WithTagResolver.

type TypeError

type TypeError struct {
	Errors []string
}

TypeError is returned when one or more YAML values cannot be assigned to the target Go types. Errors contains a message for each failed conversion.

func (*TypeError) Error

func (e *TypeError) Error() string

func (*TypeError) Is

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

type UnknownFieldError

type UnknownFieldError struct {
	Field string
	Pos   Position
}

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

func (*UnknownFieldError) Error

func (e *UnknownFieldError) Error() string

func (*UnknownFieldError) Is

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

type Unmarshaler

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

Unmarshaler is implemented by types that can decode themselves from YAML. The unmarshal function may be called with a pointer to decode the underlying YAML value into any Go type.

type UnmarshalerContext

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

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

type ValidationError

type ValidationError struct {
	Err error
	Pos Position
}

ValidationError wraps an error returned by a StructValidator with the Position of the YAML node that was decoded into the struct. This allows validation errors to be pretty-printed with FormatError just like syntax errors.

func (*ValidationError) Error

func (e *ValidationError) Error() string

func (*ValidationError) Is

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

func (*ValidationError) Unwrap

func (e *ValidationError) Unwrap() error

type WalkFunc

type WalkFunc func(n *Node) bool

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

Jump to

Keyboard shortcuts

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