37

我正在尝试构建一个 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 函数。

先感谢您。

4

7 回答 7

52

如果您使用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

于 2016-10-17T19:15:19.747 回答
42

您需要分别对待 Array 和 Hash。而且,如果您在 Rails 中,则可以使用underscore而不是 homebrew 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
于 2012-01-03T02:15:48.303 回答
11

我使用这个简短的形式:

hash.transform_keys(&:underscore)

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

hash.deep_transform_keys(&:underscore)
于 2020-09-02T23:06:31.627 回答
5

'mu is too short' 接受的答案已被转换成宝石,futurechimp 的 Plissken:

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

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

于 2014-02-20T15:14:21.137 回答
1

使用 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'} }
于 2020-08-18T11:07:33.687 回答
0

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

hash.deep_transform_keys! do |key|
  k = key.to_s.snakecase rescue key
  k.to_sym rescue key
end
于 2018-01-24T09:22:43.863 回答
0

这适用于对象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
于 2021-02-12T12:06:40.850 回答