在Ruby中将嵌套哈希键从CamelCase转换为snake_case

Posted

技术标签:

【中文标题】在Ruby中将嵌套哈希键从CamelCase转换为snake_case【英文标题】:Converting nested hash keys from CamelCase to snake_case in Ruby 【发布时间】:2012-02-01 04:15:51 【问题描述】:

我正在尝试构建一个 API 包装器 gem,但在将哈希键从 API 返回的 JSON 转换为更 Rubyish 格式时遇到问题。

JSON 包含多层嵌套,包括哈希和数组。我想要做的是将所有键递归转换为snake_case以便于使用。

这是我目前得到的:

def convert_hash_keys(value)
  return value if (not value.is_a?(Array) and not value.is_a?(Hash))
  result = value.inject() do |new, (key, value)|
    new[to_snake_case(key.to_s).to_sym] = convert_hash_keys(value)
    new
  end
  result
end

上面调用这个方法将字符串转换为snake_case:

def to_snake_case(string)
  string.gsub(/::/, '/').
  gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
  gsub(/([a-z\d])([A-Z])/,'\1_\2').
  tr("-", "_").
  downcase
end

理想情况下,结果将类似于以下内容:

hash = :HashKey => :NestedHashKey => [:Key => "value"]

convert_hash_keys(hash)
# => :hash_key => :nested_hash_key => [:key => "value"]

我弄错了递归,我尝试过的这种解决方案的每个版本要么不转换第一级以外的符号,要么过度尝试转换整个哈希,包括值。

如果可能,尝试在帮助类中解决所有这些问题,而不是修改实际的 Hash 和 String 函数。

提前谢谢你。

【问题讨论】:

在做任何其他事情之前,if (not ... and not ...) 是使用德摩根定律的完美场所。你应该写unless ... or ... 【参考方案1】:

如果您使用 Rails

哈希示例:camelCase 到 snake_case

hash =  camelCase: 'value1', changeMe: 'value2' 

hash.transform_keys  |key| key.to_s.underscore 
# =>  "camel_case" => "value1", "change_me" => "value2" 

来源: http://apidock.com/rails/v4.0.2/Hash/transform_keys

对于嵌套属性使用 deep_transform_keys 而不是 transform_keys,例如:

hash =  camelCase: 'value1', changeMe:  hereToo:  andMe: 'thanks'   

hash.deep_transform_keys  |key| key.to_s.underscore 
# => "camel_case"=>"value1", "change_me"=>"here_too"=>"and_me"=>"thanks"

来源:http://apidock.com/rails/v4.2.7/Hash/deep_transform_keys

【讨论】:

这只是转换第一级哈希,嵌套的哈希键仍然保留为驼峰式。 这对于高层来说是可以的,但对于嵌套散列也使用 deep_transform_keys。 hash.deep_transform_keys |key| key.to_s.underscore 【参考方案2】:

你需要分别对待 Array 和 Hash。而且,如果你在 Rails 中,你可以使用underscore 而不是你的自制软件to_snake_case。先来个降噪小帮手:

def underscore_key(k)
  k.to_s.underscore.to_sym
  # Or, if you're not in Rails:
  # to_snake_case(k.to_s).to_sym
end

如果您的哈希将包含不是符号或字符串的键,那么您可以适当地修改 underscore_key

如果你有一个数组,那么你只想递归地将convert_hash_keys 应用于数组的每个元素;如果你有一个哈希,你想用underscore_key修复键并将convert_hash_keys应用于每个值;如果你有其他东西,那么你想通过原封不动地传递它:

def convert_hash_keys(value)
  case value
    when Array
      value.map  |v| convert_hash_keys(v) 
      # or `value.map(&method(:convert_hash_keys))`
    when Hash
      Hash[value.map  |k, v| [underscore_key(k), convert_hash_keys(v)] ]
    else
      value
   end
end

【讨论】:

像魅力一样工作。非常感谢你。我没有在 Rails 中这样做,但我相信我用于 to_snake_case 的代码来自 Rails underscore 方法。 @Andrew:to_snake_case 中的string.gsub(/::/, '/') 让我觉得你对to_snake_case 的来源是正确的。欢迎来到 SO,祝您住宿愉快。 不需要使用 Rails,只需要使用 ActiveSupport,它让我们可以选择例程。 require 'active_support/core_ext/string/inflections' 应该这样做:'FooBar'.underscore => "foo_bar" 问题在于,当我真正需要一种方法时,我需要整个 ActiveSupport gem。【参考方案3】:

我使用这个简短的形式:

hash.transform_keys(&:underscore)

而且,正如@Shanaka Kuruwita 所指出的,要深度转换所有嵌套的哈希:

hash.deep_transform_keys(&:underscore)

【讨论】:

^ 这是最好的解决方案)旧答案在 2012 年是正确的)) 是的,现在这是更好的答案。【参考方案4】:

“mu is too short”接受的答案已转换为宝石,futurechimp 的 Plissken:

https://github.com/futurechimp/plissken/blob/master/lib/plissken/ext/hash/to_snake_keys.rb

这看起来应该在 Rails 之外工作,因为包含下划线功能。

【讨论】:

【参考方案5】:

使用 deep_transform_keys 进行递归转换。

transform_keys 只在高层转换

hash =  camelCase: 'value1', changeMe: nestedMe: 'value2' 

hash.transform_keys  |key| key.to_s.underscore 
# =>  "camel_case" => "value1", "change_me" => nestedMe: 'value2' 

deep_transform_keys 将更深入并转换所有嵌套哈希。

hash =  camelCase: 'value1', changeMe: nestedMe: 'value2' 

hash.deep_transform_keys  |key| key.to_s.underscore 
# =>  "camel_case" => "value1", "change_me" => nested_me: 'value2' 

【讨论】:

【参考方案6】:

如果您使用的是 active_support 库,则可以使用 deep_transform_keys!像这样:

hash.deep_transform_keys! do |key|
  k = key.to_s.snakecase rescue key
  k.to_sym rescue key
end

【讨论】:

请注意,snakecase 应该是 underscore,至少在 Rails 6 中是这样。【参考方案7】:

这适用于对象的camelCasesnake_case 深层嵌套键,这对于 JSON API 非常有用:

def camelize_keys(object)
  deep_transform_keys_in_object!(object)  |key| key.to_s.camelize(:lower) 
end

def snakecase_keys(object)
  deep_transform_keys_in_object!(object)  |key| key.to_s.underscore.to_sym 
end

def deep_transform_keys_in_object!(object, &block)
  case object
  when Hash
    object.keys.each do |key|
      value = object.delete(key)
      object[yield(key)] = deep_transform_keys_in_object!(value, &block)
    end
    object
  when Array
    object.map!  |e| deep_transform_keys_in_object!(e, &block) 
  else
    object
  end
end

【讨论】:

以上是关于在Ruby中将嵌套哈希键从CamelCase转换为snake_case的主要内容,如果未能解决你的问题,请参考以下文章

ruby 将嵌套数组转换为哈希

正则表达式在 Redshift 中将 CamelCase 转换为蛇形案例

如何在 R 中将 not.camel.case 转换为 CamelCase

将circe中json对象的所有键从“下划线”转换为“驼峰式”

用于在 java 中将 CamelCase 转换为 camel_case 的正则表达式

在 php __autoload() 中将 CamelCase 转换为 under_score_case