【问题标题】:Process nested hash to convert all values to strings处理嵌套哈希以将所有值转换为字符串
【发布时间】:2016-01-04 16:06:18
【问题描述】:

我有以下代码,它采用哈希并将所有 转换为字符串。

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'

看起来该对象已被展平,然后被赋予了对其自身的引用,这导致我的规范中出现堆栈级别错误。

如何获得所需的输出?

【问题讨论】:

  • 你有什么问题?
  • 为什么要使用实例变量?
  • 这样每次它在方法上递归时都会将返回结果附加到实例变量并使用对象参数构建它
  • 实例变量搞砸了,因为在调用 .each 期间,@result 的值可能会发生变化,因此您最终会将数据附加到错误的位置

标签: ruby-on-rails ruby hash


【解决方案1】:

stringify_values 的更简单实现可以是 - 假设它始终是一个 Hash。该函数利用 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

不确定 value 是数组时的期望是什么,因为 Array#to_s 也会将数组作为字符串提供,无论是否可取,您都可以决定并稍微调整一下解决方案。

【讨论】:

  • NoMethodError: undefined method 'deep_merge' for {:post=>{:id=>123, :text=>"foobar"}}:Hash
  • @mudasobwa 它是 Rails 主动支持的扩展。 OP 的问题被标记为rails
【解决方案2】:

有两个问题。首先:第一次调用后的@values 将始终包含您在第一次调用中克隆的对象,因此无论您对obj 变量做什么,最终您都会收到一个克隆的@values 对象(这是因为您的电话中有||= 运营商)。第二:如果你删除它并且会做@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

【讨论】:

  • 我明白了,所以它是导致问题的实例变量。你知道为什么在这种情况下输出不同吗?
  • 希望我能接受这两个但只选择一个班轮。我能做的就是给你点赞
  • 我已将我的反对票更改为赞成票。感谢您改进答案!
  • 一个好的答案解释了为什么解决方案有效。 Stack Overflow 的目的不是将发布者的损坏代码转化为工作代码,而是帮助他们了解代码损坏的原因以及如何修复它,从而使他们成为更好的程序员。仅代码的答案可能完成,但不太可能。
  • @WandMaker 在我的代码中从未使用过.tap(为了摆脱我通常使用的临时变量.each_with_object),感谢您的建议
【解决方案3】:

如果您想要一个不需要 ActiveSupport 的简单解决方案,您可以使用 each_with_object 在一行中完成此操作:

obj.each_with_object({}) { |(k,v),m| m[k] = v.to_s }

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

【讨论】:

    【解决方案4】:

    如果您知道将值转换为字符串,我会使用 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]"
    

    其实不知道你是不是真的要抢Arrays?

    【讨论】:

      猜你喜欢
      • 2019-08-26
      • 2018-11-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-10-21
      • 1970-01-01
      • 2019-07-01
      • 1970-01-01
      相关资源
      最近更新 更多