go-yaml Documentation

repository·main·Indexed 19 days ago

https://github.com/yaml/go-yaml

A high-performance YAML support library for Go, based on a pure Go port of the LibYAML C library, providing encoding and decoding for YAML 1.1 and 1.2. It includes a CLI tool for inspecting and transforming YAML data, a Node-level API for AST manipulation, and a 3-stage processing pipeline consisting of a Scanner, Parser, and Emitter. The documentation covers basic loading and dumping, streaming with functional options, and migration from v3 to v4.

Tokens
26.9K
Snippets
78
Records
121
Agent score
49%

What's inside go-yaml

  1. Understand the intermediate data representations in go-yaml

    main

    The go-yaml processing pipeline transforms data through several intermediate forms. Understanding these forms is essential for debugging or extending the library's internal stages:

    1. Bytes: Raw input (UTF-8, UTF-16LE, or UTF-16BE) or UTF-8 encoded output.
    2. Token: Lexical tokens produced by the Scanner. Contains type, position (Mark), and raw values.
    3. Event: Syntactic events produced by the Parser. Contains resolved tags (full URI), comments, and structural information.
    4. Node: A tree structure produced by the Composer. Represents the YAML document as a hierarchy of Node objects (Document, Sequence, Mapping, Scalar, Alias, or Stream).
    5. Repr (Representation Graph): A conceptual stage where Node trees have undergone tag resolution via the resolve() process.
    6. Native Value: The final Go language values (structs, maps, slices, etc.) produced by the Load stack or consumed by the Dump stack.
  2. Work with the YAML Node-level API

    main

    For low-level manipulation of the YAML Abstract Syntax Tree (AST), use the Node-level API. This is useful when you need to preserve comments, handle complex structures programmatically, or perform strict unmarshaling in custom types.

    Key patterns include:

    • node.Load(): Loads YAML into a Node representation. When used with WithKnownFields(), it allows for strict field checking even within custom UnmarshalYAML implementations.
    • node.Dump(): Encodes Node structures into YAML with specific formatting options.
    • Programmatic Building: You can manually construct complex Node structures and then dump them.
  3. Understand the libyaml testing framework architecture

    main

    The internal/libyaml testing framework uses a data-driven approach to separate test logic from test data. This allows for adding new test cases by simply creating YAML files without writing new Go code.

    Core Architecture:

    1. Test Data: Stored in YAML files within the testdata/ directory.
    2. Test Logic: Implemented in Go files (*_test.go).
    3. One-to-one Pairing: Each test data file (e.g., testdata/foo.yaml) has a corresponding Go test file (foo_test.go).

    Key Components:

    • TestCase struct: An umbrella structure that uses interface{} fields to hold various test types. The framework performs post-processing to convert these generic fields into specific types like WantEvents, WantTokens, or WantSpecs after loading.
  4. Understand comment types and classification

    main

    The go-yaml library classifies comments based on their position relative to YAML nodes to ensure they can be preserved during round-tripping or presented in a Node tree.

    Public Comment Types

    • HeadComment: Lines preceding a node with no blank line separation (e.g., # comment\nkey: value).
    • LineComment: Comments on the same line as a node, following its value (e.g., key: value # inline comment).
    • FootComment: Comments appearing after a node but before any blank lines (e.g., key: value\n# trailing comment).

    Internal Comment Types (Parsing only)

    • TailComment: A foot comment at the end of a block mapping value.
    • stem_comment: A comment on an entry immediately preceding a nested structure.
  5. Understand the go-yaml Dump Stack (Marshaling)

    main

    The Dump stack is the pipeline that transforms Go values into YAML text. It follows a push-based architecture where lower stages push data to higher stages. The sequence of stages is:

    1. Representer: Converts Go values into Events. It handles type dispatch, field filtering (including ,inline tags), key sorting, and style selection.
    2. Serializer: Converts Node trees into Events. It performs tag elision checks and determines when to use flow style.
    3. Emitter: Converts Events into UTF-8 YAML text, handling line wrapping, indentation, and final style selection.
    4. Writer: Flushes the output buffer to the final destination.
  6. How comments flow through the load stack

    main

    Comments are processed through a pipeline to ensure they are correctly associated with the appropriate YAML nodes:

    1. Scanner: Identifies and classifies comments (Head, Foot, or Line) based on indentation, flow context (e.g., inside [...] or {...}), and empty line boundaries.
    2. Parser: Accumulates comment tokens and attaches them to events. It handles special logic like splitting document headers at empty lines and transforming sequence entry line comments into head comments.
    3. Composer: Transfers comments from events to the final Node tree. It applies reassignment rules, such as moving dedented comments from a key's FootComment to the parent mapping's FootComment, or moving a mapping's FootComment to its last key.
  7. Understand the go-yaml Load Stack (Unmarshaling)

    main

    The Load stack is the pipeline that transforms raw YAML text into native Go values. It follows a pull-based architecture where each stage requests data from the stage below it. The sequence of stages is:

    1. Reader: Handles encoding detection (UTF-8, UTF-16LE, UTF-16BE via BOM), UTF-8 conversion, and input buffering.
    2. Scanner: Performs lexical analysis, converting bytes into Tokens while tracking indentation, flow levels, and comments.
    3. Parser: Converts Tokens into Events using an LL(1) grammar.
    4. Composer: Builds Node trees from the Event stream. It handles tag normalization, anchor registration, and comment transfer.
    5. Resolver: A function used during the process to infer implicit tags from scalar content.
    6. Constructor: The final stage that converts the Node tree into native Go values. It performs type coercion, handles custom Unmarshalers, and enforces constraints.
  8. Configure YAML behavior using Options

    main

    The go-yaml options system allows fine-grained control over formatting and parsing. Options are applied left-to-right; later options override earlier ones.

    Version Presets

    You can quickly switch between version-specific formatting styles:

    • yaml.WithV2Defaults(): 2-space indent, non-compact sequences.
    • yaml.WithV3Defaults(): 4-space indent, non-compact sequences.
    • yaml.WithV4Defaults(): 2-space indent, compact sequences.

    Individual Options

    • Dumping: WithIndent(int), WithCompactSeqIndent(), WithLineWidth(int), WithUnicode(bool).
    • Loading: WithKnownFields(), WithSingleDocument(), WithUniqueKeys().

    Loading Options from YAML

    You can define your configuration via a YAML string using yaml.OptsYAML(yamlString).

    // Mix presets with overrides
    dumper, _ := yaml.NewDumper(w, 
        yaml.WithV3Defaults(), 
        yaml.WithIndent(2), // This wins
    )
    
    // Load options from a YAML string
    configYAML := "indent: 3\nknown-fields: true"
    opts, _ := yaml.OptsYAML(configYAML)
    data, _ := yaml.Dump(&config, opts)
  9. Understand the go-yaml processing pipeline

    main

    YAML processing in go-yaml is not a single monolithic operation but a multi-stage pipeline of transformations. Both loading (YAML to Go) and dumping (Go to YAML) follow a sequence of stages where each stage consumes one data representation and produces another.

    Loading Pipeline (YAML → Native Go)

    1. Reader: Converts raw bytes to Unicode code points (handles UTF-8, UTF-16LE, UTF-16BE).
    2. Scanner: Performs lexical analysis, producing a stream of Tokens (e.g., SCALAR_TOKEN, KEY_TOKEN).
    3. Parser: Applies YAML grammar to tokens to produce a stream of Events (e.g., MAPPING_START_EVENT). This is the actual "parsing" stage.
    4. Composer: Builds a tree/graph of Nodes (Document, Mapping, Sequence, Scalar, Alias) and resolves anchors/aliases.
    5. Resolver: Processes tags (e.g., converting 42 to !!int) to create the Representation Graph (Repr).
    6. Constructor: Converts the resolved nodes into native Go values (structs, maps, slices, etc.).

    Dumping Pipeline (Native Go → YAML)

    1. Representer: Converts Go values into a tagged Node tree. Supports MarshalYAML and encoding.TextMarshaler.
    2. Desolver: (v4+) Removes tags that can be safely inferred from content to produce cleaner YAML.
    3. Serializer: Linearizes the node tree into a stream of Events.
    4. Emitter: Converts events into formatted Unicode code points.
    5. Writer: Converts code points back into raw bytes for output.
  10. Mix presets with individual options

    main

    When configuring a yaml.NewDumper or yaml.NewLoader, you can combine preset functions (like yaml.WithV3Defaults()) with specific overrides.

    Crucial Rule: Options are applied left-to-right. A later option will override any conflicting setting from an earlier option.

    // Start with v3 defaults (4-space), then override to 2-space
    dumper, _ := yaml.NewDumper(writer,
        yaml.WithV3Defaults(),
        yaml.WithIndent(2),  // This wins
    )
  11. How to choose between different YAML APIs

    main

    The choice of API depends on your data volume and whether you need custom configuration:

    ReaderWriterConfigurableUse Case
    LoadDumpYesSingle or multi-doc with options
    NewLoaderNewDumperYesLarge files, continuous streams
    UnmarshalMarshalNoQuick conversions, preset behavior
    NewDecoderNewEncoderNoMulti-doc streams, preset behavior
    • Use Dump/Load for config files, API responses, or test data where you need options but not streaming.
    • Use NewDumper/NewLoader for large files, network streams, or processing documents incrementally.
    • Use Marshal/Unmarshal for quick scripts or upgrading existing v3 code where default formatting is sufficient.
  12. Access Stream Metadata with StreamNodes

    main

    v4 introduces StreamNode, which allows access to stream-level metadata like encoding, %YAML version directives, and %TAG directives. This metadata is stored in Node.Stream and is only non-nil when the node is a StreamNode.

    To use this, you must enable stream nodes during loading using yaml.WithStreamNodes(). When enabled, the loader emits nodes in a pattern: [Stream, Doc, Stream, Doc, ..., Stream].

    loader := yaml.NewLoader(reader, yaml.WithStreamNodes())
    for {
        var node yaml.Node
        err := loader.Load(&node)
        if errors.Is(err, io.EOF) {
            break
        }
        if node.Kind == yaml.StreamNode && node.Stream != nil {
            enc := node.Stream.Encoding
            ver := node.Stream.Version        // *yaml.VersionDirective
            tags := node.Stream.TagDirectives // []yaml.TagDirective
        }
    }