Why not just re-marshal?¶
The obvious approach to editing YAML in Go is:
var m map[string]any
yaml.Unmarshal(src, &m)
m["server"].(map[string]any)["port"] = 9090
out, _ := yaml.Marshal(m)
It is three lines and it is wrong for any file a human maintains.
What that costs¶
Everything the author wrote that is not data:
- all comments, since
map[string]anyhas nowhere to put them - key order, since Go maps are unordered and marshalling sorts them
- quoting and block styles, since a string is just a string
- anchors and aliases, expanded into duplicated content
A config file with a carefully explained twenty-line preamble comes back with none of it. Worse, it comes back reordered, so the diff is the whole file and nobody can see what actually changed.
Why the obvious libraries do not fix it¶
Reaching for a different YAML library does not help, because the mangling is not a bug in any of them — it is what a value model is for.
Every Go YAML library that can round-trip a document was measured against the same eight real config files; the table, construct by construct, is in Should you use yamldoc?. The short version: the best of them returns five of eight files byte-identical and changes one line on twenty-four of seventy single-key edits, because a tree that is re-emitted cannot leave alone what it does not model.
The pattern every other ecosystem settled on¶
Two libraries, not one:
| Ecosystem | Values | Editing |
|---|---|---|
| Rust | toml |
toml_edit |
| HashiCorp | hclsyntax |
hclwrite |
| Python | tomli |
tomlkit |
Rust is the instructive case. The toml crate was built on toml_edit for two
major versions, then deliberately unwound — parsing Cargo.toml went from 542µs
to 267µs once the value model stopped carrying format-preservation machinery it
did not need.
The lesson is that these are genuinely different jobs. A value model should be fast and lossy; an editing model should be faithful and is allowed to be slower. Trying to be both makes a worse version of each.
yamldoc is the editing half. Keep using your usual library for decoding into
structs — that is what it is good at.
When re-marshalling is the right answer¶
If the file is machine-written and machine-read, re-marshal it. It is simpler and faster, and there is nothing to preserve.
Reach for yamldoc when a human wrote the file, or will read it, or has to
review the diff.