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