How a written value is spelt¶
Every write goes in as a Go value and comes out as YAML text. This page is the complete set of rules for what that text is. The contract behind all of them is one sentence: the value you write reads back as the same type and value under the document's own YAML version, and the source around it is not touched. Everything below is how the spelling is chosen to keep that true while looking like something a person would have written.
The rules are pinned by spelling_docs_test.go, which runs every row of the
tables below; if the engine and this page disagree, the test fails.
Scalar kinds¶
| Go value | Written as | Notes |
|---|---|---|
string |
plain, double-quoted, or a literal block | See Strings |
int, int64, uint… |
decimal: 42 |
Exact; never through a float |
*big.Int beyond 64 bits |
its decimal digits: 1180591620717411303424 |
Exact |
float32, float64 |
positional with a fractional part: 1.5, 3.0, 0.1 |
The shortest decimal that reads back bit-identical (D90) |
a float past Go's %g thresholds |
signed exponent: 1.0e+21, 1.0e-5 |
Still a float under Core, JSON and YAML 1.1 |
bool |
true / false |
|
nil |
null |
Over an empty value (k:) the value stays empty, which already means null |
time.Time |
!<tag:yaml.org,2002:timestamp> 2001-01-02T03:04:05Z under YAML 1.2; 2001-01-02T03:04:05Z under YAML 1.1 |
Core has no implicit timestamps, so the tag is explicit and verbatim; YAML 1.1's schema recognises the form |
[]byte |
!<tag:yaml.org,2002:binary> aGk= |
Base64, explicit tag in every profile |
A type the profile cannot represent, such as a float under Failsafe, is
refused with ErrUnsupportedType rather than written as text.
Strings¶
A string is written plain when plain is safe, double-quoted when it is not, and as a literal block when it holds line breaks and a block can go there.
Plain¶
The text is written bare when, under the document's version, it would read back as exactly that string and nothing else:
k: new
k: 😀
k: a b # a tab is fine in a YAML 1.2 plain scalar
k: yes # a string under YAML 1.2, where yes is not a boolean
Double-quoted¶
Anything that would change meaning or shape as a plain scalar goes in double quotes, with escapes where needed:
| Text | Written | Why |
|---|---|---|
42, null, 1e3 |
"42", "null" |
Would read back as a number or null |
yes under YAML 1.1 |
"yes" |
A boolean there |
a: b, a # b |
"a: b", "a # b" |
Would become a mapping or a comment |
"" |
"" |
An empty plain scalar is null |
lead, trail |
" lead", "trail " |
Plain scalars lose edge whitespace |
a b under YAML 1.1 |
"a\tb" |
Tabs are not plain there |
a U+202E b (a bidi override or zero-width character) |
"a\u202eb" |
Escaped so a reader can see it (CVE-2021-42574) |
l1\r\nl2 |
"l1\r\nl2" |
A carriage return has no block spelling |
Single quotes are never chosen for a new value; they are kept where they already are (see Existing scalars).
Literal block¶
A string containing \n is written as a literal block scalar when the target
is in block context: below a mapping key or on a sequence item's dash line,
one indentation step in, using the document's own step and line ending.
| Text | Written | Chomping |
|---|---|---|
l1\nl2\n |
k: \|l1l2 |
clip: one trailing break |
l1\nl2 |
k: \|-l1l2 |
strip: none |
l1\n\n |
k: \|+l1(empty line) |
keep: all of them |
l1\nl2\n |
k: \|2l1l2 |
an indent indicator, because the first line begins with whitespace |
p1\n\np2\n |
k: \|p1(empty line) p2 |
interior blank lines are written bare, never as indentation |
The block's final line break replaces the scalar's own, so the file gains no blank line. The body reads back exactly, including under YAML 1.1.
It stays double-quoted, with \n escapes, where a block cannot go:
| Situation | Written |
|---|---|
inside a flow collection: {k: old} |
{k: "l1\nl2\n"} |
| as a mapping key | "l1\nl2": v |
a comment follows the scalar on its line: k: old # c |
k: "l1\nl2\n" # c |
| the scalar is the last thing in the file, with no line break after it | k: "l1\nl2\n" |
a line ends in a space or tab: l1 \nl2\n |
"l1 \nl2\n", since a block cannot tell that from layout |
the text contains \r |
"l1\r\nl2" |
| the existing scalar is single-quoted | see below |
Existing scalars¶
Replacing a scalar keeps its spelling where the new value fits it. The rule is D60: the style is retained, no tag is added to keep it, and a style that cannot hold the value gives way.
| Existing | New string | Written |
|---|---|---|
plain k: old |
any | as for a new value above |
empty k: |
any | as for a new value; nil leaves it empty |
single-quoted k: 'old' |
one line | k: 'new', k: 'yes', k: '42': single quotes hold anything but a break or a control character |
| single-quoted | with line breaks | k: 'l1l2': the quotes stay and each break is folded as a blank line, which reads back exactly |
| single-quoted | with \r or an invisible character |
double-quoted; a tab is fine in single quotes under either version |
double-quoted k: "old" |
one line | k: "new", k: "😀": double quotes stay |
| double-quoted | with line breaks | a literal block, as for a new value |
literal k: \| |
anything, one line or many | the body is replaced and the block stays: k: \|-new; the chomping indicator follows the new text's trailing breaks, and an indent indicator is added when its first line begins with whitespace |
folded k: > |
anything | as literal, with > kept: k: >-new |
| any block | with \r or an invisible character |
double-quoted |
A non-string value written over a quoted or block scalar takes its own
spelling (k: 'old' → k: 42); a timestamp or binary keeps a quoted style
for its content where there was one (!<…timestamp> '2001-01-02T03:04:05Z').
An existing scalar with an explicit tag keeps it when the new value is the
same type (!!str old → !!str 42: the tag already makes it a string), and refuses a value of a different
type with ErrTagIntent. An anchored scalar keeps its anchor.
Mapping keys¶
A new key is written plain when its text reads back as the same string key,
and double-quoted otherwise, so 09 or true as a key becomes "09": and
"true": rather than an integer or boolean key. An existing key is never
respelt; it is matched by value, so StringStep("port") finds port: and
KeyStep(IntKey(8080)) finds 8080: or 0x1f90:.
Collections¶
| Written into | New collection |
|---|---|
| a block mapping or sequence | block style, one indentation step in, the step inferred from the container's own nesting (two spaces when there is nothing to infer) |
| a block sequence, a mapping value | compact: - key: value, later entries aligned under the first |
| a flow collection | flow style: {a: 1}, [a, b] |
an empty stream (CreateDocument) |
block style, two-space indentation, LF, a final newline; a --- only after a comment-only header |
| anywhere, when empty | {} or [] |
A Go map has no order, so its keys are written sorted; MappingInput writes
entries in the order given. A scalar with a comment on its line that becomes
a collection is written in flow style on that line, so the comment keeps its
owner.
Version differences¶
| YAML 1.2 (Core) | YAML 1.1 | |
|---|---|---|
yes, no, on, off as strings |
plain | double-quoted |
| a tab in a string | plain | double-quoted |
time.Time |
explicit verbatim tag | plain, the schema recognises it |
| floats | 1.5, 1.0e+21 |
the same spelling: both grammars accept it |
Document.Version reports which rules a document is under.