ZetCode

Ruby YAML

last modified October 18, 2026

YAML (YAML Ain't Markup Language) is a human-friendly data serialization format widely used for configuration files and data exchange. Ruby ships with the Psych library, which is accessed through require 'yaml'. This guide covers loading and dumping YAML, the difference between YAML.load and YAML.safe_load, file I/O, YAML syntax, multi-line strings, and error handling. For a companion article on JSON, see the Ruby JSON tutorial.

Loading YAML with YAML.load

YAML.load parses a YAML string and returns the corresponding Ruby objects. Mappings become hashes, sequences become arrays, and scalars are converted to strings, integers, floats, booleans, or nil. The require 'yaml' line must come first. This example parses a small document describing a person.

basic_load.rb
#!/usr/bin/env ruby

require 'yaml'

yaml_text = <<~YAML
  name: Alice
  age: 30
  languages:
    - Ruby
    - Python
  active: true
YAML

data = YAML.load(yaml_text)
puts data.inspect
puts data["name"]
puts data["languages"].first
puts data["age"].class

YAML uses indentation (spaces only, never tabs) for structure. A key: value line makes a mapping, a dash starts a sequence item, and nesting is created by indenting under a parent key. Flow style such as [admin, editor] is also valid. Lists, nested mappings, and multi-line strings (| keeps line breaks, > folds them) all map to Ruby arrays, hashes, and strings. Symbol keys are written with a leading colon (:host: localhost) and need permitted_classes: [Symbol] under safe_load. The heredoc holds the YAML text, and YAML.load turns it into a Ruby hash. Keys are strings by default, the list becomes an array, and age is parsed as an Integer.

$ ./basic_load.rb
{"name"=>"Alice", "age"=>30, "languages"=>["Ruby", "Python"], "active"=>true}
Alice
Ruby
Integer

Why YAML.safe_load Is Preferred

YAML.load can instantiate arbitrary Ruby objects and symbols from tags in the input, which makes it unsafe for untrusted data. Ruby 3.0 still allows it for trusted input, but for anything that comes from users or the network, YAML.safe_load is the right choice. It only builds basic types (strings, numbers, booleans, arrays, hashes, and nil) and raises Psych::DisallowedClass for anything else. This example shows both behaviors.

safe_load.rb
#!/usr/bin/env ruby

require 'yaml'

untrusted = "greeting: hello\nnumbers: [1, 2, 3]\n"
puts YAML.safe_load(untrusted).inspect

begin
  YAML.safe_load(":mode: fast\n")
rescue Psych::DisallowedClass => e
  puts "Rejected: #{e.message}"
end

config = YAML.safe_load(":mode: fast\n", permitted_classes: [Symbol])
puts config.inspect

begin
  YAML.safe_load("--- !ruby/object:Object {}\n")
rescue Psych::DisallowedClass => e
  puts "Rejected: #{e.message}"
end

Plain data loads without any extra options. The symbol key :mode is rejected until Symbol is listed in permitted_classes:, so only permit classes you actually expect. The !ruby/object tag is refused entirely. On Ruby 3.0.2, YAML.load accepts both of these inputs, which is why it should not be used on untrusted data.

$ ./safe_load.rb
{"greeting"=>"hello", "numbers"=>[1, 2, 3]}
Rejected: Tried to load unspecified class: Symbol
{:mode=>"fast"}
Rejected: Tried to load unspecified class: Object

Dumping Ruby Objects to YAML

YAML.dump serializes a Ruby object to a YAML string, and every object also responds to to_yaml. Hashes become mappings, arrays become sequences with dashes, and nested structures are indented. The example dumps a nested hash and an array of mixed types.

dump_to_yaml.rb
#!/usr/bin/env ruby

require 'yaml'

book = {
  "title" => "Ruby Basics",
  "year" => 2024,
  "tags" => ["ruby", "tutorial"],
  "author" => { "name" => "Jan", "email" => "jan@example.com" }
}

puts YAML.dump(book)
puts "---"
puts [1, "two", 3.0, nil, true].to_yaml

YAML.dump(book) writes the nested hash with the nested author mapping indented beneath its key. The array is dumped with to_yaml, which produces the same format. The nil value appears as an empty entry after the dash.

$ ./dump_to_yaml.rb
---
title: Ruby Basics
year: 2024
tags:
- ruby
- tutorial
author:
  name: Jan
  email: jan@example.com
---
---
- 1
- two
- 3.0
- 
- true

Reading and Writing YAML Files

YAML.load_file reads and parses a file in one step. To write, pass the result of to_yaml to File.write. Note that load_file uses the same unrestricted loader as YAML.load, so for files you do not control, prefer YAML.safe_load(File.read(path)). The example writes a settings file, reads it back, and deletes it.

file_io.rb
#!/usr/bin/env ruby

require 'yaml'

settings = { "theme" => "dark", "font_size" => 14, "plugins" => ["git", "lint"] }
File.write("settings.yml", settings.to_yaml)

loaded = YAML.load_file("settings.yml")
puts loaded.inspect
puts "Theme: #{loaded['theme']}"
File.delete("settings.yml")

File.write stores the YAML produced by to_yaml. YAML.load_file reads it back into an equivalent hash. The file is removed at the end so the example leaves nothing behind.

$ ./file_io.rb
{"theme"=>"dark", "font_size"=>14, "plugins"=>["git", "lint"]}
Theme: dark

Multi-line Strings

YAML has two block scalar styles for multi-line text. The literal style (|) keeps each line break exactly as written. The folded style (>) joins lines into a single paragraph with spaces, and a blank line inside it produces a newline. Both styles end with a single newline by default. The example loads one of each.

multiline.rb
#!/usr/bin/env ruby

require 'yaml'

yaml_text = <<~YAML
  literal: |
    line one
    line two
  folded: >
    this text is
    folded into one line
YAML

data = YAML.safe_load(yaml_text)
p data["literal"]
p data["folded"]

The literal value keeps its two lines separated by \n. The folded value joins its two source lines into one, with a space in place of the line break.

$ ./multiline.rb
"line one\nline two\n"
"this text is folded into one line\n"

Handling Syntax Errors

Malformed YAML raises Psych::SyntaxError. Rescue it when parsing user-supplied text so the application can report the problem instead of crashing. The example shows two malformed documents being rescued.

bad_input.rb
#!/usr/bin/env ruby

require 'yaml'

bad = "name: Alice\n  age: 30\n - broken"
begin
  YAML.safe_load(bad)
rescue Psych::SyntaxError => e
  puts "Syntax error caught"
  puts e.class
end

begin
  YAML.safe_load("key: [unclosed")
rescue Psych::SyntaxError => e
  puts "Still caught: #{e.class}"
end

Both inputs are invalid YAML, and Psych::SyntaxError is raised for each. Catching it lets the program recover, log the error, or ask for corrected input.

$ ./bad_input.rb
Syntax error caught
Psych::SyntaxError
Still caught: Psych::SyntaxError

List all Ruby tutorials.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.