Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Data Formats (JSON, YAML, TOML, XML, CSV)  Module 5 – YAML 1.2 and its traps

YAML essentials: mappings, sequences, scalars

Write YAML that parses as you mean it: mappings and sequences indented with spaces, flow style and JSON, three ways to quote, and several documents in one file.

  • Beginner
  • 15 minutes
  • Examples run with Node.js 24.21.0, quickjs 0.32.0, Python 3.14.8, Pyodide 314.0.7, Ruby 4.0.7 and sql.js 1.14.2
  • By MySmartCoPilot
Free lesson, Pro answers: 15 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Write nested block mappings and sequences, indented with spaces only
  • Write the same data in flow style, and predict how a YAML parser reads JSON text
  • Choose plain, single-quoted or double-quoted scalars and escape characters correctly
  • Split one file into several documents with --- and ..., and load every one of them

Before you start

What you’ll learn · Before you start

What you will learn

  • Write nested block mappings and sequences, indented with spaces only
  • Write the same data in flow style, and predict how a YAML parser reads JSON text
  • Choose plain, single-quoted or double-quoted scalars and escape characters correctly
  • Split one file into several documents with --- and ..., and load every one of them

Before you start

On this page

The Pune branch of the bookshop keeps its settings in a YAML file of 13 lines. Put a tab in place of the four spaces before one key, and Psych, the YAML parser in Ruby’s standard library, refuses the whole file and points at line 7, one line above the tab. YAML reads a file’s structure from its indentation and the type of each value from its text, so a single character decides whether a file loads and what each value becomes.

You will build that file up step by step: nest mappings and sequences with spaces, write the same data in flow style and read JSON as YAML, choose between plain, single-quoted and double-quoted strings, and keep several documents in one file. Every example was read by Psych, so each rule comes with what a real parser did with it.

Indentation is the structure: mappings and sequences

A YAML document is built from three kinds of node. A mapping: A YAML collection of keys with their values, written key: value one pair per line in block style, or {key: value} in flow style. The keys of one mapping must be unique. holds keys with their values, written key: value, one pair per line. A sequence: A YAML collection of items in order, written one per line after a dash and a space in block style, or between square brackets in flow style. holds items in order, each written after a dash and a space. A scalar: A single YAML value that is not a collection: text that the parser turns into a string, a number, a boolean or null. is one value: a string, a number, true or false, or null. Indentation, counted in spaces, decides which node a line belongs to. Here is the Pune branch’s file:

branch.yaml: the settings of the Pune branch YAML · branch/branch.yaml
# Settings of the Pune branch of the bookshop
branch: Pune
opens: "09:30"
gift_wrap: true
counters: 3
printers:
  - name: front-desk
    paper: A4
  - name: stockroom
    paper: label
manager:
  name: Asha Kulkarni
  email: pune@bookshop.example

The root is a mapping whose keys have no indentation. printers has no value on its own line, so its value is the block indented under it: a sequence of two items. Each item is a mapping, and its second key, paper, sits exactly under name, the first key after the dash. manager holds a mapping of two keys. Step through how a parser decides, line by line:

Interactive · step by step How the parser reads branch.yaml, line by line Step through the file. Each step reads a line, compares its indentation with the open blocks, and adds a node to the tree.
How the parser reads branch.yaml, line by line, step 8 of 8. Showing Root mapping (branch, opens, gift_wrap…), branch: Pune, printers:, Sequence, Item: front-desk (name and paper), Item: stockroom (name and paper), manager:, Mapping. In focus: Mapping.Root mappingbranch, opens, gift_wrap…branch: Punea key and a plain scalarprinters:no value on its lineSequencedashes at indent 2Item: front-deskname and paperItem: stockroomname and papermanager:back at indent 0Mappingname and email

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. Line 2: a key at indent 0. The comment on line 1 is skipped. branch: Pune has no indentation and holds a key, a colon and a space, so the parser opens the root mapping and gives it its first key.
  2. Lines 3 to 5: more keys. opens, gift_wrap and counters have no indentation either, so they are keys of the same mapping. Each value is a scalar on the same line as its key.
  3. Line 6: a key without a value. printers: has nothing after its colon. Its value has to be the block on the next lines, indented further than the key.
  4. Line 7: a dash at indent 2. A dash and a space after 2 spaces start a block sequence, indented further than printers, so the sequence is its value. After the dash, name: front-desk opens a mapping: the first item.
  5. Line 8: in line with name. paper: A4 is indented 4 spaces, which puts it exactly under name, so it is a second key of the same item. A tab in place of those four spaces makes Psych refuse the file.
  6. Lines 9 and 10: a second dash. Another dash at indent 2 adds a second item to the same sequence, with its own name and paper.
  7. Line 11: back at indent 0. manager: has no indentation, less than the sequence, so the sequence ends and manager is the next key of the root mapping.
  8. Lines 12 and 13: nested keys. name and email are indented 2 spaces under manager, so they form a mapping that is its value. The end of the file closes every open block: the tree is the JSON that read_branch.rb printed.

The program below asks Psych for the same file and prints what it built as JSON, whose brackets and braces show the nesting. Then it makes two small changes and reads the file again each time:

What Psych builds from branch.yaml, and two small changes Ruby · branch/read_branch.rb
# Read branch.yaml with Psych, the YAML parser in Ruby's standard library,
# and print the data it built as JSON, so the nesting is easy to see.
require "yaml"
require "json"

text = File.read("branch.yaml")
puts JSON.pretty_generate(YAML.safe_load(text))

# Two small changes, each read again.
changes = {
  "a tab before paper on line 8" => ["    paper: A4", "\tpaper: A4"],
  "paper on line 10 moved to column 0" => ["    paper: label", "paper: label"],
}
changes.each do |what, (from, to)|
  puts
  puts "With #{what}:"
  begin
    data = YAML.safe_load(text.sub(from, to))
    puts "  loaded. printers: #{data['printers'].inspect}"
    puts "  and a new root key paper: #{data['paper'].inspect}"
  rescue Psych::SyntaxError => e
    puts "  refused: #{e.message}"
  end
end

Output

{
  "branch": "Pune",
  "opens": "09:30",
  "gift_wrap": true,
  "counters": 3,
  "printers": [
    {
      "name": "front-desk",
      "paper": "A4"
    },
    {
      "name": "stockroom",
      "paper": "label"
    }
  ],
  "manager": {
    "name": "Asha Kulkarni",
    "email": "pune@bookshop.example"
  }
}

With a tab before paper on line 8:
  refused: (<unknown>): found a tab character that violates indentation while scanning a plain scalar at line 7 column 11

With paper on line 10 moved to column 0:
  loaded. printers: [{"name" => "front-desk", "paper" => "A4"}, {"name" => "stockroom"}]
  and a new root key paper: "label"

Recorded with ruby 4.0.7 (revision 229531a6cf) +PRISM [arm64-darwin23] on macOS 26 arm64. To run it yourself: ruby read_branch.rb

The JSON is the tree the steps built: a mapping of six keys, a sequence of two mappings under printers and a mapping under manager. Psych also typed the plain values: 3 became an integer and true a boolean, while "09:30" stayed a string because of its quotes.

The first change, a tab before paper, stopped everything. The YAML 1.2.2 specification forbids tabs in indentation “to maintain portability”, because different systems treat tabs differently. Psych’s message names line 7, column 11: the start of front-desk, the value it was still reading when it met the tab, not the tab itself. When an error points at a line that looks right, look at the start of the next line.

The second change is worse, because nothing complains. With paper: label moved to the left margin, the line is no longer inside the second item: it is a new key of the root mapping, and the stockroom printer has no paper at all.

Trusting a file because it loaded

Wrong: move a line one indentation level left and assume an error would tell you. Right: indentation is the data, so a line at the wrong level can load without a word, as paper: label did. Compare what a parser built with what you meant, for example by printing it as JSON as read_branch.rb does.

Three rules cover most block layouts. Any number of spaces nests a block, two in this file, and every key of one mapping must start at the same indentation. A key needs a colon followed by a space or a line break, so name:front-desk is one string, not a key and a value. And a mapping inside a sequence item lines its keys up with the first key after the dash, not with the dash.

YAML Validator & Formatter Paste a YAML file to see each error with its line and column, tabs in the indentation included.

Checkpoint

Check: Indentation as structure

  1. Question 1 of 2 What does a parser build from this file?

    Read the code, then choose one answer.

    printers:
      - name: front-desk
        paper: A4
      - name: stockroom
    paper: label
    A hint

    Look at the indentation of the last line, and which open block it can belong to.

    Show the answer: Question 1 of 2

    Answer: Two printers, the second without paper, and a root key paper with the value label

    Indentation is the data. A line at indent 0 joins the root mapping, so the stockroom printer has no paper and the root gains a key called paper. Print what the parser built, as JSON for example, to catch a change like this.

  2. Question 2 of 2 Psych refused branch.yaml with a tab before paper on line 8, but its message named line 7, column 11. Why line 7?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It was still reading the value front-desk, which starts at line 7, column 11, when it met the tab

    A parser reports where it was when it gave up. Psych was scanning the plain scalar front-desk, so it named that scalar's start. When an error points at a line that looks right, look at the start of the next line.

Flow style looks like JSON, and JSON text is YAML 1.2

Block style is one way to write collections. flow style: YAML written with brackets and braces, [a, b] for a sequence and {k: v} for a mapping, with commas between entries, as JSON is. Block style uses indentation and dashes instead. is the other: a sequence in square brackets, a mapping in braces, entries separated by commas, on one line or several. Here are the two printers both ways:

The printers in block style and in flow style

YAML (printers-block.yaml) · flow/printers-block.yaml

# The printers of the Pune branch in block style, as in branch.yaml
printers:
  - name: front-desk
    paper: A4
  - name: stockroom
    paper: label

YAML (printers-flow.yaml) · flow/printers-flow.yaml

# The same printers in flow style: brackets and braces, as in JSON
printers: [{name: front-desk, paper: A4}, {name: stockroom, paper: label}]

Flow style is compact, which suits short lists such as tags: [fiction, travel]; block style suits anything people edit line by line, because each line holds one entry. Inside brackets and braces a comma ends a plain value, so a value that holds a comma needs quotes.

Flow style is also where JSON meets YAML. A JSON object is a flow mapping whose keys and strings are double-quoted scalars, and a JSON array is a flow sequence. The YAML 1.2 specification says its “primary focus was making YAML a strict superset of JSON”, so a parser that follows YAML 1.2 reads JSON text and builds the same values. The exception is an object that repeats a name: RFC 8259 only says that names should be unique, while YAML requires the keys of a mapping to be unique. This program compares the two printer files, reads a flow list with a comma inside a value, reads the bookshop’s order.json with Ruby’s JSON parser and with Psych, and finally tries three numbers:

Flow style, and JSON read as YAML Ruby · flow/flow_and_json.rb
# Flow style, and JSON text read by a YAML parser (Psych) and by a JSON parser.
require "yaml"
require "json"

block = YAML.safe_load(File.read("printers-block.yaml"))
flow = YAML.safe_load(File.read("printers-flow.yaml"))
puts "Flow printers equal the block ones: #{flow == block}"

# Inside brackets a comma ends a plain scalar: quote a value that holds one.
puts YAML.safe_load('tags: [fiction, kids, teens, "kids, teens"]').inspect

text = File.read("order.json")
puts "order.json, YAML equals JSON: #{YAML.safe_load(text) == JSON.parse(text)}"
puts "receipt as YAML read it: #{YAML.safe_load(text)['receipt']}"

# Three numbers that JSON allows, with no space after the colons. Psych
# follows YAML 1.1, whose floats need a dot and a signed exponent.
numbers = '{"a":1.5e+3,"b":1e3,"c":2E-3}'
puts "JSON.parse:     #{JSON.parse(numbers)}"
puts "YAML.safe_load: #{YAML.safe_load(numbers)}"

Output

Flow printers equal the block ones: true
{"tags" => ["fiction", "kids", "teens", "kids, teens"]}
order.json, YAML equals JSON: true
receipt as YAML read it: https://bookshop.example/r/BK-10042
JSON.parse:     {"a" => 1500.0, "b" => 1000.0, "c" => 0.002}
YAML.safe_load: {"a" => 1500.0, "b" => "1e3", "c" => "2E-3"}

Recorded with ruby 4.0.7 (revision 229531a6cf) +PRISM [arm64-darwin23] on macOS 26 arm64. To run it yourself: ruby flow_and_json.rb

The block and flow printers are the same data, and the list shows the comma rule: kids, teens written plain gave two items, while "kids, teens" in quotes gave one. The order read as YAML equals the order read as JSON, \/ escapes and all, and Psych also read the numbers written with no space after their colons: the specification changes list both details as new in YAML 1.2, the version that made YAML a superset of JSON.

The last two lines show the catch. Psych follows YAML 1.1, whose float type needs a dot and a sign before the exponent, so the JSON numbers 1e3 and 2E-3 came back as the strings "1e3" and "2E-3", while 1.5e+3 became 1500.0. A parser that follows the YAML 1.2 core schema reads all three as numbers, because its float pattern makes the dot and the sign optional. JSON text is YAML 1.2; a YAML 1.1 parser reads nearly all of it the same way, and the exceptions are values like these.

Hand a JSON file to a YAML 1.1 parser with care

Before a YAML 1.1 parser such as Psych reads JSON from another program, check how that program writes floats. A float written without a dot, such as 1e3 or 2E-3, reaches a YAML 1.1 reader as a string. Read JSON with a JSON parser, and keep YAML parsers for YAML.

Checkpoint

Check: Flow style and JSON as YAML

  1. Question 1 of 2 How many items does tags: [fiction, kids, teens] hold?

    Type a number.

    A hint

    In flow style a comma ends a plain value.

    Show the answer: Question 1 of 2

    Answer: 3 items

    Inside brackets each comma ends a plain scalar, so the list holds fiction, kids and teens. flow_and_json.rb read kids, teens written plain as two items and "kids, teens" in quotes as one.

  2. Question 2 of 2 Psych reads the JSON text {"n": 1e3} with YAML.safe_load. What is n?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The string "1e3"

    JSON text is YAML 1.2, so a YAML 1.2 parser reads 1e3 as 1000.0. Psych follows the YAML 1.1 float pattern, which needs a dot and a signed exponent: it returned "1e3" and "2E-3" as strings and 1.5e+3 as 1500.0.

Plain, single-quoted or double-quoted: choose by what the text holds

Most strings in a YAML file need no quotes at all. A plain scalar: A YAML scalar written without quotes. It cannot contain a colon and a space or a space and #, cannot start with most indicator characters, and the parser types it from its text. is text without quotes, and it may hold spaces, digits and most punctuation. Two pairs of characters are off limits inside it, because they mean something else there: a colon followed by a space would start a key, and a space followed by # starts a comment. A plain scalar also cannot start with most of the characters that YAML uses as indicators, such as [, {, &, *, !, |, >, ', ", %, @ and the backtick. When the text breaks one of these rules, quote it. This file holds fourteen values of the bookshop, each with a comment that says why it is written as it is:

quoting.yaml: fourteen values and how each is written YAML · quoting/quoting.yaml
# Strings of the bookshop, and how each one is written
title: The Little Prince          # plain: spaces inside are fine
sku: BK#101                       # plain: no space before #, so no comment
note: price #1 item               # plain: " #" starts a comment
command: 'echo Total: 5'          # single quotes: ": " is fine inside
apostrophe: 'it''s here'          # single quotes: '' stands for one '
win_path: 'C:\new\table'          # single quotes: backslashes stay as they are
win_path2: "C:\\new\\table"       # double quotes: \\ for each backslash
two_lines: "line one\nline two"   # double quotes: \n is a line break
city: "\u092A\u0941\u0923\u0947"  # double quotes: \u and four hex digits
padded: '  two spaces'            # quotes keep the leading spaces
qty: 2                            # plain digits: an integer
qty_text: "2"                     # quoted digits: a string
empty: ''                         # an empty string
nothing:                          # no value at all: null

The program prints the Ruby class and the value that Psych built from each line, then tries the command without quotes:

What Psych builds from each value Ruby · quoting/quoting.rb
# What Psych builds from each line of quoting.yaml: the Ruby class and the value.
require "yaml"

YAML.safe_load(File.read("quoting.yaml")).each do |key, value|
  puts format("%-11s %-9s %s", key, value.class, value.inspect)
end

# The same command written plain: ": " inside a plain scalar is refused.
begin
  YAML.safe_load("command: echo Total: 5\n")
rescue Psych::SyntaxError => e
  puts
  puts "Plain command: #{e.message}"
end

Output

title       String    "The Little Prince"
sku         String    "BK#101"
note        String    "price"
command     String    "echo Total: 5"
apostrophe  String    "it's here"
win_path    String    "C:\\new\\table"
win_path2   String    "C:\\new\\table"
two_lines   String    "line one\nline two"
city        String    "पुणे"
padded      String    "  two spaces"
qty         Integer   2
qty_text    String    "2"
empty       String    ""
nothing     NilClass  nil

Plain command: (<unknown>): mapping values are not allowed in this context at line 1 column 20

Recorded with ruby 4.0.7 (revision 229531a6cf) +PRISM [arm64-darwin23] on macOS 26 arm64. To run it yourself: ruby quoting.rb

Read the output against the comments. BK#101 stayed whole, because no space came before its #, while price #1 item lost everything after price: the rest of the line was a comment. The single-quoted command kept its : , and 'it''s here' shows the only escape single quotes have: two quotes for one. A backslash means nothing in single quotes, so 'C:\new\table' kept both of them, while in double quotes each backslash had to be written \\ to give the same string.

Double quotes are the only style with escape sequence: A backslash and the characters after it, standing for one character inside a string, such as \n for a line feed or \u0041 for the letter A. More in “JSON syntax and data model (RFC 8259)”: \n became a line break, and the four \u escapes spelled पुणे, the name Pune in Devanagari. Quotes of either kind keep leading spaces, which a plain scalar loses.

The last four values are about types rather than quotes. Plain 2 became an integer, quoted "2" a string, '' an empty string, and a key with no value at all nil, Ruby’s null. A quoted scalar is always a string. A plain one is given its type by the parser, from its text, and that is where the traps of this module live. Last, the plain command echo Total: 5: its second : made Psych refuse the whole document.

A colon and a space inside a plain value

Wrong: command: echo Total: 5, which Psych refused with “mapping values are not allowed in this context”. Right: command: 'echo Total: 5'. A long command reads better as a block scalar, the subject of the next lesson.

All fourteen lines follow one rule of thumb:

Key idea

Write a value plain when it is ordinary text. Quote it when it holds : or #, starts with an indicator, needs leading spaces or must stay a string, and use double quotes when you need an escape.

Checkpoint

Check: Plain, single-quoted and double-quoted scalars

  1. Question 1 of 2 What value does note: price #1 item give?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: price

    quoting.rb printed "price" for this line. Quote a value that holds " #", or write the # without a space before it when that is what the text means, as in BK#101.

  2. Question 2 of 2 A value must hold a line break and a tab, written on one line of the file. Which style can write it?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Double quotes, as "one\ntwo\tthree"

    The specification interprets escape sequences only in double-quoted scalars. In single quotes and plain scalars a backslash is an ordinary character.

One file can hold several documents

A YAML stream: A YAML text holding zero or more documents. A line --- starts a document and a line ... ends one, so one file can carry several documents. holds zero or more documents. A line --- starts a document, and a line ... ends one without starting the next, which lets a program that reads a stream as it arrives act on each document as soon as it is complete. Kubernetes uses streams to keep related resources together: its documentation groups them “in the same file (separated by --- in YAML)”. Here are the opening hours of three branches in one file:

branches.yaml: three documents in one file YAML · stream/branches.yaml
# Opening hours of three branches, one document each
branch: Pune
opens: "09:30"
---
branch: Kochi
opens: "10:00"
...
---
# A document can start with a comment, too
branch: Indore
opens: "09:00"

The first document needs no ---, because a stream may begin with a bare document. After ..., YAML 1.2 would accept a bare document too, but YAML 1.1, whose rules Psych follows here, says that every later document begins with ---, so write it. A comment may stand at the start of a document, like anywhere else outside a scalar. The program reads the file twice, once as a stream and once as a single document, then tries two broken streams:

Reading every document, and reading one Ruby · stream/stream.rb
# One file, three documents: read them all, then see what reading one gives.
require "yaml"

text = File.read("branches.yaml")
docs = YAML.safe_load_stream(text)
puts "safe_load_stream: #{docs.size} documents"
docs.each { |doc| puts "  #{doc['branch']} opens at #{doc['opens']}" }

puts "safe_load:        #{YAML.safe_load(text).inspect}"

# A tab in the second document only: does the stream give back the other two?
broken = text.sub('opens: "10:00"', "\topens: \"10:00\"")
got = []
begin
  YAML.safe_load_stream(broken) { |doc| got << doc }
rescue Psych::SyntaxError => e
  puts "With a tab in document 2: #{got.size} documents, then #{e.class}"
end

# YAML 1.2 also accepts a bare document after "...". Psych, a YAML 1.1 parser, wants "---".
begin
  YAML.safe_load_stream("branch: Pune\n...\nbranch: Kochi\n")
rescue Psych::SyntaxError => e
  puts "A bare document after ...: #{e.problem}"
end

Output

safe_load_stream: 3 documents
  Pune opens at 09:30
  Kochi opens at 10:00
  Indore opens at 09:00
safe_load:        {"branch" => "Pune", "opens" => "09:30"}
With a tab in document 2: 0 documents, then Psych::SyntaxError
A bare document after ...: did not find expected <document start>

Recorded with ruby 4.0.7 (revision 229531a6cf) +PRISM [arm64-darwin23] on macOS 26 arm64. To run it yourself: ruby stream.rb

safe_load_stream returned all three documents in order. safe_load, given the same text, returned the first one and said nothing about the other two, so a loader for one document is the wrong tool for a stream, and nothing warns you. The line about the tab shows that Psych reads a stream as a whole: with a tab in the second document, safe_load_stream handed over none of the three, not even the first, although the program gave it a block to receive each document. The last line is the --- rule at work: Psych refused a bare document after .... Psych also has a load_stream. Of the two, safe_load_stream is the one that takes the same safety options as safe_load, and the last lesson of this module shows why that matters for files you did not write.

Each document of a stream stands on its own. The specification changes list documents that “are now independent of each other”: a document does not inherit the directives of the one before, and an anchor, the reuse marker of the anchors lesson, works only inside its own document.

Checkpoint

Check: Documents and streams

  1. Question 1 of 2 How many documents does YAML.safe_load_stream return for this text?

    Type a number.

    branch: Pune
    ---
    branch: Kochi
    ...
    ---
    # Indore
    branch: Indore
    Show the answer: Question 1 of 2

    Answer: 3 documents

    The first document is bare, --- starts the second, ... ends it, and the next --- starts the third. The comment belongs to the third document. branches.yaml has the same shape, and safe_load_stream returned 3 documents.

  2. Question 2 of 2 What does YAML.safe_load return when it is given the three documents of branches.yaml?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The first document only, with no warning

    Use safe_load_stream for a stream. A loader for one document returns the first and drops the others silently.

Practise: write strings that read back unchanged

A program that writes YAML has to decide, for every string, whether it can go out plain. The exercise asks you to make that decision with the rules of this lesson, and to write a double-quoted scalar, escapes included, whenever the plain form would change the string. Its sample tests use values from this lesson, such as echo Total: 5 and price #1 item:

Exercise · Easy · Python

Write a string so that YAML reads it back unchanged

A program that writes YAML has to decide, for every string, whether it can go out as a plain scalar or needs quotes. Write yaml_scalar(s) in scalar.py. It returns the text to write after key: so that a YAML parser reads back exactly the string s:

  • When s is safe as a plain scalar, return it unchanged.
  • Otherwise return it in double quotes, with each \ written as \\, each " as \", a line break as \n and a tab as \t.

Use the rules of the lesson. A string is not safe as a plain scalar when:

  1. it is empty, or it starts or ends with a space;
  2. it starts with one of the indicator characters - ? : , [ ] { } # & * ! | > ' " % @ or a backtick;
  3. it contains : or #, or it ends with :;
  4. it contains a line break or a tab;
  5. a parser could read it as another type: in any mix of capitals it is null, ~, true, false, yes, no, on, off, y or n, or it starts with a digit, a + or a ., as +7, .5 and .inf do.

The rules are cautious on purpose: quoting a string that did not need quotes is harmless, and leaving one plain that needed them changes your data.

Starter code · scalar.py

def yaml_scalar(s):
    """The text to write after "key: " so that a YAML parser reads back exactly the string s."""
    return s
The sample tests · test_scalar.py
from scalar import yaml_scalar


def test_plain_text_stays_plain():
    """leaves ordinary text, a # without a space and a backslash plain"""
    assert yaml_scalar("The Little Prince") == "The Little Prince"
    assert yaml_scalar("BK#101") == "BK#101"
    assert yaml_scalar("C:\\new") == "C:\\new"
    assert yaml_scalar("Norway") == "Norway"


def test_colon_and_comment():
    """quotes text with ': ' or ' #' in it, or a colon at the end"""
    assert yaml_scalar("echo Total: 5") == '"echo Total: 5"'
    assert yaml_scalar("price #1 item") == '"price #1 item"'
    assert yaml_scalar("note:") == '"note:"'


def test_indicators_at_the_start():
    """quotes text that starts with an indicator character"""
    assert yaml_scalar("*bold*") == '"*bold*"'
    assert yaml_scalar("@pune") == '"@pune"'
    assert yaml_scalar("[draft]") == '"[draft]"'
    assert yaml_scalar("- item") == '"- item"'


def test_values_of_other_types():
    """quotes text that a parser would read as null, a boolean or a number"""
    assert yaml_scalar("2") == '"2"'
    assert yaml_scalar("3.10") == '"3.10"'
    assert yaml_scalar("NO") == '"NO"'
    assert yaml_scalar("null") == '"null"'
    assert yaml_scalar("~") == '"~"'
    assert yaml_scalar("-5") == '"-5"'
    assert yaml_scalar(".5") == '".5"'
    assert yaml_scalar(".inf") == '".inf"'
    assert yaml_scalar("+7") == '"+7"'


def test_empty_and_spaces():
    """quotes the empty string and text with a space at either end"""
    assert yaml_scalar("") == '""'
    assert yaml_scalar(" two") == '" two"'
    assert yaml_scalar("two ") == '"two "'


def test_escapes_inside_double_quotes():
    """escapes backslashes, quotes, line breaks and tabs inside the quotes"""
    assert yaml_scalar("line one\nline two") == '"line one\\nline two"'
    assert yaml_scalar("tab\there") == '"tab\\there"'
    assert yaml_scalar('"quoted"') == '"\\"quoted\\""'
    assert yaml_scalar("C:\\new: x") == '"C:\\\\new: x"'
A hint

Write one small check per rule and quote when any of them is true. For rule 5, compare s.lower() with a set of the ten words, then look at s[0]. When you escape, replace the backslash first: replacing it after the quotes would double the backslashes you have just added.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Then check the four ideas, from indentation to streams, in a short session that starts with whichever idea you are least sure of and explains each choice as you make it:

Practice that adapts to you

Practise this lesson

9 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 9 In branch.yaml, which line starts the second item of the printers sequence?

    Choose one answer.

    Show the answer: Question 1 of 9

    Answer: - name: stockroom

    In block style each item of a sequence starts with a dash and a space. Keys of a mapping inside the item line up with the first key after the dash.

  2. Question 2 of 9 The email line below is indented one space further than name. What happens when a parser reads it?

    Read the code, then choose one answer.

    manager:
      name: Asha Kulkarni
       email: pune@bookshop.example
    Show the answer: Question 2 of 9

    Answer: The parser refuses the file: email cannot be a key of the same mapping, and its : cannot be part of the name

    Every key of one mapping starts at the same indentation. A line indented further than the keys continues the value before it, so the parser meets ": " inside a plain scalar and refuses the document, the same rule that refused the plain command echo Total: 5.

  3. Question 3 of 9 Which flow form holds the same data as a block sequence of one printer whose keys are name: a and paper: A4?

    Choose one answer.

    Show the answer: Question 3 of 9

    Answer: printers: [{name: a, paper: A4}]

    In flow style [ ] is a sequence and { } a mapping, so a list of mappings is [{…}, {…}]. A key: value pair written directly inside brackets becomes a mapping of its own.

  4. Question 4 of 9 A service reads JSON messages with Psych. Which of these JSON numbers will it get back as strings? Choose every one.

    Choose every answer that is right.

    Show the answer: Question 4 of 9

    Answer:

    • 1E6
    • 1e3

    The YAML 1.1 float pattern needs a dot, and an exponent, when there is one, needs a sign. JSON allows exponents without either, so a JSON reader should be a JSON parser, or a YAML 1.2 parser, whose float pattern makes both optional.

  5. Question 5 of 9 How do you write the text it's here as a single-quoted scalar?

    Choose one answer.

    Show the answer: Question 5 of 9

    Answer: 'it''s here'

    Inside single quotes, '' stands for one ' and nothing else is an escape. Use double quotes when you need escapes such as \n.

  6. Question 6 of 9 What does path: "C:\new\table" give, written in double quotes without doubled backslashes?

    Choose one answer.

    Show the answer: Question 6 of 9

    Answer: C: then a line break, then ew, a tab and able

    Double quotes read every backslash as the start of an escape. Write each one as \\, as win_path2 did, or use single quotes, where a backslash is an ordinary character.

  7. Question 7 of 9 What types does Psych give qty: 2 and qty_text: "2"?

    Choose one answer.

    Show the answer: Question 7 of 9

    Answer: An integer and a string

    Quotes decide the type: a quoted scalar is a string, a plain one is typed by the parser. That is why quoting is the first defence against the type traps of this module.

  8. Question 8 of 9 What does a line holding only ... do in a YAML stream?

    Choose one answer.

    Show the answer: Question 8 of 9

    Answer: It ends the current document without starting a new one

    --- starts a document and ... ends one. A program that reads a stream as it arrives can act on a document as soon as its end marker comes.

  9. Question 9 of 9 A file holds one document for each of 40 branches. Which Psych call gives you all 40 as plain Ruby values?

    Choose one answer.

    Show the answer: Question 9 of 9

    Answer: YAML.safe_load_stream(text)

    Read a stream with a stream reader. safe_load_stream returned the three documents of branches.yaml, while safe_load returned only Pune's.

Interview questions

Warm-up (fresher to 2–5 years): is every JSON document valid YAML?

For YAML 1.2, yes, with one exception: the specification made YAML a strict superset of JSON, so a JSON object is a flow mapping, an array a flow sequence, and a YAML 1.2 parser builds the same values from the same text. The exception is an object that repeats a name, which JSON only discourages and YAML forbids. Older YAML 1.1 parsers read almost all JSON the same way, but not every number: Psych, which follows YAML 1.1, read the JSON numbers 1e3 and 2E-3 as strings, because a YAML 1.1 float needs a dot and a signed exponent. The reverse is not true: most YAML is not JSON, since comments, plain strings, block style and anchors have no JSON form. In practice, read JSON with a JSON parser, and when YAML must accept JSON, test the parser you use with the number forms the other side writes.

Two harder questions follow: one about an error that points at the wrong line, one about keeping many documents in one file. Decide what you would check first, then open each one’s first step:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    A deploy stops with a YAML syntax error at line 112, column 9 of a 300-line settings file, but line 112 looks correct. How do you find the real cause, and what would stop the next such mistake before it reaches a deploy?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    Your service keeps one YAML file per branch, 40 files in all. A colleague wants a single file with 40 documents instead. What changes for the code that loads it, for errors and for the people who edit it, and what would you decide?

    Model answer, Pro. First step and follow-ups

Flashcards · 6 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. What happens when YAML indentation contains a tab?

    The parser stops: YAML indents with spaces only. A line indented to the wrong level is worse, because it can load silently as different data.

  2. Is every JSON document valid YAML?

    For YAML 1.2, yes, apart from an object with a repeated name, which YAML forbids. A YAML 1.1 parser such as Psych read the JSON numbers 1e3 and 2E-3 as strings.

  3. When must a YAML value be quoted?

    When it holds ": " or " #", starts with an indicator character, needs leading spaces, or has to stay a string.

  4. What can single quotes and double quotes each escape in YAML?

    Single quotes escape only a single quote, written twice. Double quotes are the only style with escape sequences, such as \n and \u.

  5. Do 2 and "2" load as the same value in YAML?

    No. A quoted scalar is always a string, while a plain one gets its type from the parser, so 2 becomes an integer.

  6. How do you read a YAML file that holds several documents with Psych?

    With safe_load_stream, which returns every document in order. safe_load returns only the first and says nothing about the rest. --- starts a document and ... ends one.

Key takeaways

  • A YAML document is mappings, sequences and scalars, nested by indentation in spaces; a tab in the indentation stops the parser, and a line at the wrong level can load silently as different data.
  • Flow style writes the same collections with brackets, braces and commas; JSON text is YAML 1.2, but a YAML 1.1 parser such as Psych read the JSON numbers 1e3 and 2E-3 as strings.
  • Plain scalars cannot hold : or # or start with an indicator; single quotes escape only '', and double quotes are the only style with escape sequences such as \n and \u.
  • A quoted scalar is always a string, while a plain one is typed by the parser, so 2 and "2" load differently.
  • --- starts a document and ... ends one; safe_load_stream reads every document of a stream, while safe_load quietly returns only the first.

Terms in this lesson

flow style
YAML written with brackets and braces, [a, b] for a sequence and {k: v} for a mapping, with commas between entries, as JSON is. Block style uses indentation and dashes instead.
mapping
A YAML collection of keys with their values, written key: value one pair per line in block style, or {key: value} in flow style. The keys of one mapping must be unique.
plain scalar
A YAML scalar written without quotes. It cannot contain a colon and a space or a space and #, cannot start with most indicator characters, and the parser types it from its text.
scalar
A single YAML value that is not a collection: text that the parser turns into a string, a number, a boolean or null.
sequence
A YAML collection of items in order, written one per line after a dash and a space in block style, or between square brackets in flow style.
stream
A YAML text holding zero or more documents. A line --- starts a document and a line ... ends one, so one file can carry several documents.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.