Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add :stringify_names option to convert symbol keys to string for dumping #621

Merged
merged 1 commit into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions lib/psych.rb
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,10 @@ def self.parse_stream yaml, filename: nil, &block
#
# Default: <tt>false</tt>.
#
# [<tt>:stringify_names</tt>] Dump symbol keys in Hash objects as string.
#
# Default: <tt>false</tt>.
#
# Example:
#
# # Dump an array, get back a YAML string
Expand All @@ -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
Expand Down Expand Up @@ -562,6 +569,10 @@ def self.dump o, io = nil, options = {}
#
# Default: <tt>false</tt>.
#
# [<tt>:stringify_names</tt>] Dump symbol keys in Hash objects as string.
#
# Default: <tt>false</tt>.
#
# Example:
#
# # Dump an array, get back a YAML string
Expand All @@ -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
Expand Down
5 changes: 3 additions & 2 deletions lib/psych/visitors/yaml_tree.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down
26 changes: 26 additions & 0 deletions test/psych/test_psych.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions test/psych/test_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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