diff --git a/lib/psych.rb b/lib/psych.rb
index 4a2ab585..c860b8df 100644
--- a/lib/psych.rb
+++ b/lib/psych.rb
@@ -489,6 +489,10 @@ def self.parse_stream yaml, filename: nil, &block
#
# Default: false.
#
+ # [:stringify_names] Dump symbol keys in Hash objects as string.
+ #
+ # Default: false.
+ #
# Example:
#
# # Dump an array, get back a YAML string
@@ -502,6 +506,9 @@ def self.parse_stream yaml, filename: nil, &block
#
# # Dump an array to an IO with indentation set
# Psych.dump(['a', ['b']], StringIO.new, indentation: 3)
+ #
+ # # Dump hash with symbol keys as string
+ # Psych.dump({a: "b"}, stringify_names: true) # => "---\na: b\n"
def self.dump o, io = nil, options = {}
if Hash === io
options = io
@@ -562,6 +569,10 @@ def self.dump o, io = nil, options = {}
#
# Default: false.
#
+ # [:stringify_names] Dump symbol keys in Hash objects as string.
+ #
+ # Default: false.
+ #
# Example:
#
# # Dump an array, get back a YAML string
@@ -575,6 +586,9 @@ def self.dump o, io = nil, options = {}
#
# # Dump an array to an IO with indentation set
# Psych.safe_dump(['a', ['b']], StringIO.new, indentation: 3)
+ #
+ # # Dump hash with symbol keys as string
+ # Psych.dump({a: "b"}, stringify_names: true) # => "---\na: b\n"
def self.safe_dump o, io = nil, options = {}
if Hash === io
options = io
diff --git a/lib/psych/visitors/yaml_tree.rb b/lib/psych/visitors/yaml_tree.rb
index 51491783..673c8bc6 100644
--- a/lib/psych/visitors/yaml_tree.rb
+++ b/lib/psych/visitors/yaml_tree.rb
@@ -70,6 +70,7 @@ def initialize emitter, ss, options
fail(ArgumentError, "Invalid line_width #{@line_width}, must be non-negative or -1 for unlimited.")
end
end
+ @stringify_names = options[:stringify_names]
@coders = []
@dispatch_cache = Hash.new do |h,klass|
@@ -328,7 +329,7 @@ def visit_Hash o
if o.class == ::Hash
register(o, @emitter.start_mapping(nil, nil, true, Psych::Nodes::Mapping::BLOCK))
o.each do |k,v|
- accept k
+ accept(@stringify_names && Symbol === k ? k.to_s : k)
accept v
end
@emitter.end_mapping
@@ -341,7 +342,7 @@ def visit_Psych_Set o
register(o, @emitter.start_mapping(nil, '!set', false, Psych::Nodes::Mapping::BLOCK))
o.each do |k,v|
- accept k
+ accept(@stringify_names && Symbol === k ? k.to_s : k)
accept v
end
diff --git a/test/psych/test_psych.rb b/test/psych/test_psych.rb
index c977e799..42586a87 100644
--- a/test/psych/test_psych.rb
+++ b/test/psych/test_psych.rb
@@ -430,6 +430,32 @@ def test_safe_dump_symbols
assert_match(/\A--- :foo\n(?:\.\.\.\n)?\z/, Psych.safe_dump(:foo, permitted_symbols: [:foo]))
end
+ def test_safe_dump_stringify_names
+ yaml = <<-eoyml
+---
+foo:
+ bar: bar
+ 'no': special escapes
+ 123: number
+eoyml
+
+ payload = Psych.safe_dump({
+ foo: {
+ bar: "bar",
+ no: "special escapes",
+ 123 => "number"
+ }
+ }, stringify_names: true)
+ assert_equal yaml, payload
+
+ assert_equal("---\nfoo: :bar\n", Psych.safe_dump({foo: :bar}, stringify_names: true, permitted_symbols: [:bar]))
+
+ error = assert_raise Psych::DisallowedClass do
+ Psych.safe_dump({foo: :bar}, stringify_names: true)
+ end
+ assert_equal "Tried to dump unspecified class: Symbol(:bar)", error.message
+ end
+
def test_safe_dump_aliases
x = []
x << x
diff --git a/test/psych/test_set.rb b/test/psych/test_set.rb
index 87944d83..b4968d34 100644
--- a/test/psych/test_set.rb
+++ b/test/psych/test_set.rb
@@ -46,5 +46,12 @@ def test_set_self_reference
@set['self'] = @set
assert_cycle(@set)
end
+
+ def test_stringify_names
+ @set[:symbol] = :value
+
+ assert_match(/^:symbol: :value/, Psych.dump(@set))
+ assert_match(/^symbol: :value/, Psych.dump(@set, stringify_names: true))
+ end
end
end