Reputation: 9700
I have the following code which takes a hash and turns all the values in to strings.
def stringify_values obj
@values ||= obj.clone
obj.each do |k, v|
if v.is_a?(Hash)
@values[k] = stringify_values(v)
else
@values[k] = v.to_s
end
end
return @values
end
So given the following hash:
{
post: {
id: 123,
text: 'foobar',
}
}
I get following YAML output
--- &1
:post: *1
:id: '123'
:text: 'foobar'
When I want this output
---
:post:
:id: '123'
:text: 'foobar'
It looks like the object has been flattened and then been given a reference to itself, which causes Stack level errors in my specs.
How do I get the desired output?
Upvotes: 4
Views: 5098
Reputation: 8088
If you want a simple solution without need of ActiveSupport, you can do this in one line using each_with_object
:
obj.each_with_object({}) { |(k,v),m| m[k] = v.to_s }
If you want to modify obj
in place pass obj
as the argument to each_with_object
; the above version returns a new object.
Upvotes: 0
Reputation: 18772
A simpler implementation of stringify_values
can be - assuming that it is always a Hash. This function makes use of Hash#deep_merge
method added by Active Support Core Extensions - we merge the hash with itself, so that in the block we get to inspect each value and call to_s
on it.
def stringify_values obj
obj.deep_merge(obj) {|_,_,v| v.to_s}
end
Complete working sample:
require "yaml"
require "active_support/core_ext/hash"
def stringify_values obj
obj.deep_merge(obj) {|_,_,v| v.to_s}
end
class Foo
def to_s
"I am Foo"
end
end
h = {
post: {
id: 123,
arr: [1,2,3],
text: 'foobar',
obj: { me: Foo.new}
}
}
puts YAML.dump (stringify_values h)
#=>
---
:post:
:id: '123'
:arr: "[1, 2, 3]"
:text: foobar
:obj:
:me: I am Foo
Not sure what is the expectation when value is an array, as Array#to_s
will give you array as a string as well, whether that is desirable or not, you can decide and tweak the solution a bit.
Upvotes: 8
Reputation: 15791
There are two issues. First: the @values
after the first call would always contain an object which you cloned in the first call, so in the end you will always receive a cloned @values
object, no matter what you do with the obj
variable(it's because of ||=
operator in your call). Second: if you remove it and will do @values = obj.clone
- it would still return incorrect result(deepest hash), because you are overriding existing variable call after call.
require 'yaml'
def stringify_values(obj)
temp = {}
obj.each do |k, v|
if v.is_a?(Hash)
temp[k] = stringify_values(v)
else
temp[k] = v.to_s
end
end
temp
end
hash = {
post: {
id: 123,
text: 'foobar',
}
}
puts stringify_values(hash).to_yaml
#=>
---
:post:
:id: '123'
:text: foobar
Upvotes: 4
Reputation: 121020
If you are as aware of converting values to strings, I would go with monkeypatching Hash
class:
class Hash
def stringify_values
map { |k, v| [k, Hash === v ? v.stringify_values : v.to_s] }.to_h
end
end
Now you will be able to:
require 'yaml'
{
post: {
id: 123,
text: 'foobar'
},
arr: [1, 2, 3]
}.stringify_values.to_yaml
#⇒ ---
# :post:
# :id: '123'
# :text: foobar
# :arr: "[1, 2, 3]"
In fact, I wonder whether you really want to scramble Array
s?
Upvotes: -1