Skip to content

Latest commit

 

History

History
318 lines (241 loc) · 7.04 KB

File metadata and controls

318 lines (241 loc) · 7.04 KB

Migrating from v3 to v4

This guide will help you migrate your code from go.yaml.in/yaml/v3 (or gopkg.in/yaml.v3) to go.yaml.in/yaml/v4.

Quick Migration Checklist

  • Update import path
  • Optionally migrate to new API (Load/Dump, Loader/Dumper)
  • Adjust formatting expectations or use yaml.WithV3Defaults() preset
  • Update tests

Import Path Change

v3:

import "gopkg.in/yaml.v3"
// or
import "go.yaml.in/yaml/v3"

v4:

import "go.yaml.in/yaml/v4"

Update all import statements throughout your codebase.

API Changes

Recommended: Use New API

v4 introduces a cleaner API with better naming.

Loading YAML

v3:

err := yaml.Unmarshal(data, &config)

v4:

err := yaml.Load(data, &config)

Dumping YAML

v3:

data, err := yaml.Marshal(&config)

v4:

data, err := yaml.Dump(&config)

Streaming Decoding

v3:

decoder := yaml.NewDecoder(reader)
err := decoder.Decode(&config)

v4:

loader := yaml.NewLoader(reader)
err := loader.Load(&config)

Streaming Encoding

v3:

encoder := yaml.NewEncoder(writer)
err := encoder.Encode(&config)
encoder.Close()

v4:

dumper := yaml.NewDumper(writer)
err := dumper.Dump(&config)
dumper.Close()

New Features in v4

Stream Nodes

v4 adds StreamNode, a new node kind that exposes stream-level metadata not available in v3: encoding, %YAML version directives, and %TAG directives. This is accessed via Node.Stream, which is non-nil only on StreamNode nodes.

Enable stream nodes with WithStreamNodes():

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
    }
}

With stream nodes enabled, the loader emits nodes in the pattern [Stream, Doc, Stream, Doc, ..., Stream] — one opening stream node per document boundary, plus a final closing stream node.

Functional Options

v4 introduces a functional options pattern for configuration:

// Version presets
yaml.Dump(&data, yaml.WithV2Defaults())  // Use v2 defaults
yaml.Dump(&data, yaml.WithV3Defaults())  // Use v3 defaults
yaml.Dump(&data, yaml.WithV4Defaults())  // Use v4 defaults (2-space, compact)

// Custom options
yaml.Dump(&data,
    yaml.WithIndent(4),
    yaml.WithCompactSeqIndent(false),
    yaml.WithLineWidth(100),
)

// Combine presets with overrides
yaml.Dump(&data, yaml.WithV3Defaults(), yaml.WithIndent(2))

// Loading options
yaml.Load(data, &config,
    yaml.WithKnownFields(),   // Strict field checking
    yaml.WithUniqueKeys(),    // Enforce unique keys
)

Available dump options:

  • WithIndent(n) - Set indentation spaces
  • WithCompactSeqIndent(bool) - Compact sequence indentation
  • WithLineWidth(n) - Maximum line width
  • WithUnicode(bool) - Use Unicode characters
  • WithCanonical(bool) - Canonical output format
  • WithLineBreak(lb) - Line break style (LN, CR, CRLN)
  • WithExplicitStart(bool) - Add --- document start
  • WithExplicitEnd(bool) - Add ... document end
  • WithFlowSimpleCollections(bool) - Use flow style for simple collections

Available load options:

  • WithKnownFields() - Reject unknown struct fields
  • WithUniqueKeys() - Enforce unique mapping keys
  • WithSingleDocument() - Expect only one document

Options from YAML

You can configure options using YAML:

optsYAML := `
indent: 4
compact-seq-indent: false
line-width: 100
`

opts, err := yaml.OptsYAML(optsYAML)
if err != nil {
    log.Fatal(err)
}

data, err := yaml.Dump(&config, opts)

Formatting Differences

v4 has different default formatting than v3:

Aspect v3 Default v4 Default
Indentation 4 spaces 2 spaces
Sequence style Normal Compact

Example:

# v3 default output
items:
    - name: foo
      value: 1
    - name: bar
      value: 2

# v4 default output
items:
- name: foo
  value: 1
- name: bar
  value: 2

Preserving v3 Behavior

If you need v3's formatting, use the yaml.WithV3Defaults() preset:

// Get v3-style formatting in v4
data, err := yaml.Dump(&config, yaml.WithV3Defaults())

Or customize individual options:

data, err := yaml.Dump(&config,
    yaml.WithIndent(4),
    yaml.WithCompactSeqIndent(false),
)

Backward Compatibility

All v3 APIs continue to work in v4. The classic API remains supported for simple use cases:

  • Unmarshal() - Classic API (or use Load() for more flexibility)
  • Marshal() - Classic API (or use Dump() for more flexibility)
  • NewDecoder() - Classic API (or use NewLoader() for more flexibility)
  • NewEncoder() - Classic API (or use NewDumper() for more flexibility)

You can migrate incrementally:

  1. Update import path to v4
  2. Verify tests pass
  3. Optionally migrate to new API for additional features
  4. Update TypeError.Errors handling if applicable

Migration Strategies

Strategy 1: Minimal Change (Fastest)

  1. Update import path
  2. If using TypeError.Errors directly, update that code
  3. Add yaml.WithV3Defaults() preset to maintain v3 formatting
  4. Done!
// Only change needed for basic migration
data, err := yaml.Dump(&config, yaml.WithV3Defaults())

Strategy 2: Adopt New API (Recommended)

  1. Update import path
  2. Replace UnmarshalLoad, MarshalDump
  3. Replace NewDecoderNewLoader, NewEncoderNewDumper
  4. Test with v4 defaults or choose formatting explicitly

Strategy 3: Feature Adoption (Maximum Value)

  1. Follow Strategy 2
  2. Explore new functional options
  3. Leverage structured error information
  4. Adopt version presets for different use cases

Testing Your Migration

# Run your existing tests
go test ./...

# Verify YAML output formatting
# Use the go-yaml CLI tool to compare
go install go.yaml.in/yaml/v4/cmd/go-yaml@latest
./go-yaml -n < testfile.yaml

Common Issues

Issue: Output formatting changed

Solution: Use yaml.WithV3Defaults() preset to maintain v3 formatting:

yaml.Dump(&data, yaml.WithV3Defaults())

Issue: Want more flexibility from classic API

Solution: Migrate to the new API for options support:

  • UnmarshalLoad
  • MarshalDump
  • NewDecoderNewLoader
  • NewEncoderNewDumper

Getting Help

If you encounter issues during migration:

Next Steps