YAML in depth

safe_load

YAML is JSON with comments, anchors and a friendlier syntax, and Ruby has it in the standard library through Psych. YAML.safe_load turns a document into Ruby values without building arbitrary objects:

ruby -e 'require "yaml"; p YAML.safe_load("a: 1")'

{"a" => 1}

Keys become strings, indentation is the structure, and a scalar that looks like a number becomes one. YAML.load is the old name and does not have the same protection; new code calls safe_load.

dump

YAML.dump writes the document, and the result starts with --- and lists the sequence items with a dash:

ruby -e 'require "yaml"; puts YAML.dump({"a" => 1, "list" => [1, 2]})'

--- a: 1 list: - 1 - 2

A round trip through dump and safe_load gives the same values, but not the same text: comments and key order are not preserved by a dump of a hash. For a config a person edits, that is the reason a program writes it only when asked.

Anchors and aliases

An anchor names a value and an alias reuses it. The feature is what makes a YAML config shorter, and it is off by default in safe_load because an alias can expand exponentially:

ruby -e 'require "yaml"; y = "base: &b\n x: 1\ncopy: *b\n"; p YAML.safe_load(y, aliases: true)'

{"base" => {"x" => 1}, "copy" => {"x" => 1}}

Without the option the load raises, and the message names the switch:

ruby -e 'require "yaml"; y = "base: &b\n x: 1\ncopy: *b\n"; begin; YAML.safe_load(y); rescue => e; puts e.class; puts e.message; end'

Psych::AliasesNotEnabled Alias parsing was not enabled. To enable it, pass `aliases: true` to `Psych::load` or `Psych::safe_load`.

Enable aliases only for a document you control. A config that comes from a user is a place where a small file can expand into a large structure.

Permitted classes

YAML can name a Ruby class, and safe_load refuses to build one unless it is on the list. That is the difference between a data format and a deserialisation gadget:

ruby -e 'require "yaml"; begin; YAML.safe_load("--- !ruby/object:OpenStruct\nname: Ada\n"); rescue => e; puts e.class; puts e.message; end'

Psych::DisallowedClass Tried to load unspecified class: OpenStruct

Add the class when the config really uses it:

ruby -e 'require "yaml"; require "ostruct"; p YAML.safe_load("--- !ruby/object:OpenStruct\nname: Ada\n", permitted_classes: [OpenStruct])'

#<OpenStruct name="Ada">

A project config rarely needs a class at all: strings, numbers, booleans, arrays and hashes are enough. When a config does need a Date or a Symbol, list exactly that class rather than reaching for unsafe_load.

What to watch for

YAML is indentation-sensitive, and a tab where a space belongs is a parse error with a line number. The error is usually accurate; the surprise is that an editor inserted the tab.

JSON is a subset of YAML, so a JSON document loads with safe_load, but a YAML document is not JSON. For data that travels between programs, JSON is the smaller, stricter choice; YAML earns its place in a file a person edits.

Next: CSV in depth

Article author: Arthur Isaev