将嵌套的哈希处理为将所有值转换为字符串的形式

4
我有如下代码,它接收一个哈希并将所有转换为字符串。
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

那么根据以下哈希表:

{
  post: {
    id: 123,
    text: 'foobar',
  }
}

我得到了以下的YAML输出。
--- &1
:post: *1
:id: '123'
:text: 'foobar'

当我想要这个输出时
--- 
:post: 
  :id: '123'
  :text: 'foobar'

看起来这个对象已经被压扁,然后被给予了对自身的引用,这会导致我的规范出现堆栈级别错误。

我该如何获得所需的输出?


你有什么问题? - sawa
为什么要使用实例变量? - Frederick Cheung
这样每次在方法上递归时,它都将返回结果附加到实例变量上,并使用对象参数构建它。 - Cjmarkham
1
实例变量会导致问题,因为在调用.each时,@result的值可能会发生更改,因此您最终会将数据附加到错误的位置。 - Frederick Cheung
4个回答

8

一个更简单的stringify_values实现可以是 - 假设它总是一个哈希表。该函数利用了Active Support Core Extensions添加的Hash#deep_merge方法 - 我们将哈希表与自身合并,以便在块中检查每个值并调用to_s方法。

def stringify_values obj
  obj.deep_merge(obj) {|_,_,v| v.to_s}
end

完整的工作样例:
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

当值为数组时,不确定期望是什么,因为Array#to_s也会将数组转换为字符串,无论是否希望如此,您都可以决定并稍微调整解决方案。


1
NoMethodError: undefined method 'deep_merge' for {:post=>{:id=>123, :text=>"foobar"}}:Hash - Aleksei Matiushkin
@mudasobwa 这是Rails活动支持中的一个扩展。OP的问题标记为rails - Wand Maker

4
有两个问题。首先:在第一次调用之后,@values 总是包含一个你在第一次调用中克隆的对象,所以最终你将始终收到一个克隆的 @values 对象,无论你对 obj 变量做什么(这是由于你的调用中的 ||= 运算符)。其次:如果你删除它并执行 @values = obj.clone - 它仍然会返回不正确的结果(最深层的哈希),因为你正在覆盖每次调用后的现有变量。
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

希望我能接受两个,但最终选择了单行代码。我所能做的就是点赞你的回答。 - Cjmarkham
@CarlMarkham 当然这是你的决定,我在这里是来帮忙的,而不是为了得分和勾选标记 :) - Rustam Gasanov
1
我已将我的踩转成了赞。感谢您改进了您的回答! - Jordan Running
1
一个好的答案解释了为什么解决方案有效。Stack Overflow 的目的不是将帖子中的错误代码转换为可工作的代码,而是帮助他们理解为什么代码出错以及如何修复它,从而使他们成为更好的程序员。仅有代码的答案可能会达到这个目的,但这是不太可能的。 - Jordan Running
1
@WandMaker,我从来没有在我的代码中使用过.tap(为了摆脱临时变量,我通常会使用.each_with_object),感谢您的建议。 - Rustam Gasanov
显示剩余2条评论

0
如果您想要一个不需要ActiveSupport的简单解决方案,可以使用each_with_object在一行中完成:
obj.each_with_object({}) { |(k,v),m| m[k] = v.to_s }

如果你想就地修改obj,请将obj作为参数传递给each_with_object;上面的版本返回一个新对象。


-1
如果您了解将值转换为字符串,我建议使用 monkeypatching Hash 类:
class Hash
  def stringify_values
    map { |k, v| [k, Hash === v ? v.stringify_values : v.to_s] }.to_h
  end
end

现在你将能够:

require 'yaml'
{
  post: {
    id: 123,
    text: 'foobar'
  },
  arr: [1, 2, 3]
}.stringify_values.to_yaml
#⇒ ---
#  :post:
#    :id: '123'
#    :text: foobar
#  :arr: "[1, 2, 3]"

实际上,我想知道你是否真的想要打乱数组


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接