如何使用 Ruby 中的键 deep_transform_values?
How to deep_transform_values with keys in Ruby?
Active Support 的 deep_transform_values
递归地转换哈希的所有值。但是,是否有类似的方法允许在转换时访问值的键?
我希望能够做到以下几点:
keys_not_to_transform = ['id', 'count']
response = { result: 'ok', errors: [], data: { id: '123', price: '100.0', quotes: ['1.0', '2.0'] }, count: 10 }
response.deep_transform_values! do |key, value|
# Use value's key to help decide what to do
return value if keys_not_to_transform.any? key.to_s
s = value.to_s
if s.present? && /\A[+-]?\d+(\.\d+)?\z/.match?(s)
return BigDecimal(s)
else
value
end
end
#Expected result
# =>{:result=>"ok", :errors=>[], :data=>{:id=>"123", :price=>0.1e3, :quotes=>[0.1e1, 0.2e1]}, :count=>10}
请注意,我们对转换密钥本身不感兴趣,只是在转换相应值时手头有它。
您可以像这样使用 Hash#deep_merge!
(由 ActiveSupport
提供):
keys_not_to_transform = ['id', 'count']
transform_value = lambda do |value|
s = value.to_s
if s.present? && /\A[+-]?\d+(\.\d+)?\z/.match?(s)
BigDecimal(s)
else
value
end
end
transform = Proc.new do |key,value|
if keys_not_to_transform.include? key.to_s
value
elsif value.is_a?(Array)
value.map! do |v|
v.is_a?(Hash) ? v.deep_merge!(v,&transform) : transform_value.(v)
end
else
transform_value.(value)
end
end
response = { result: 'ok', errors: [], data: { id: '123', price: '100.0', quotes: ['1.0', '2.0'], other: [{id: '124', price: '17.0'}] }, count: 10 }
response.deep_merge!(response, &transform)
这输出:
#=>{:result=>"ok", :errors=>[], :data=>{:id=>"123", :price=>0.1e3, :quotes=>[0.1e1, 0.2e1], :other=>[{:id=>"124", :price=>0.17e2}]}, :count=>10}
我只是用普通的旧 Ruby 和一些递归实现必要的转换逻辑,不需要外部依赖。例如:
def transform(hash, ignore_keys: [])
hash.each_with_object({}) do |(key, value), result|
if value.is_a?(Hash)
result[key] = transform(value, ignore_keys: ignore_keys)
elsif ignore_keys.include?(key.to_s)
result[key] = value
elsif value.to_s =~ /\A[+-]?\d+(\.\d+)?\z/
result[key] = BigDecimal(value)
else
result[key] = value
end
end
end
keys_not_to_transform = %w[id count]
response = { result: 'ok', errors: [], data: { id: '123', price: '100.0' }, count: 10 }
transform(response, ignore_keys: keys_not_to_transform)
# => {:result=>"ok", :errors=>[], :data=>{:id=>"123", :price=>#<BigDecimal:5566613bb128,'0.1E3',9(18)>}, :count=>10}
Active Support 的 deep_transform_values
递归地转换哈希的所有值。但是,是否有类似的方法允许在转换时访问值的键?
我希望能够做到以下几点:
keys_not_to_transform = ['id', 'count']
response = { result: 'ok', errors: [], data: { id: '123', price: '100.0', quotes: ['1.0', '2.0'] }, count: 10 }
response.deep_transform_values! do |key, value|
# Use value's key to help decide what to do
return value if keys_not_to_transform.any? key.to_s
s = value.to_s
if s.present? && /\A[+-]?\d+(\.\d+)?\z/.match?(s)
return BigDecimal(s)
else
value
end
end
#Expected result
# =>{:result=>"ok", :errors=>[], :data=>{:id=>"123", :price=>0.1e3, :quotes=>[0.1e1, 0.2e1]}, :count=>10}
请注意,我们对转换密钥本身不感兴趣,只是在转换相应值时手头有它。
您可以像这样使用 Hash#deep_merge!
(由 ActiveSupport
提供):
keys_not_to_transform = ['id', 'count']
transform_value = lambda do |value|
s = value.to_s
if s.present? && /\A[+-]?\d+(\.\d+)?\z/.match?(s)
BigDecimal(s)
else
value
end
end
transform = Proc.new do |key,value|
if keys_not_to_transform.include? key.to_s
value
elsif value.is_a?(Array)
value.map! do |v|
v.is_a?(Hash) ? v.deep_merge!(v,&transform) : transform_value.(v)
end
else
transform_value.(value)
end
end
response = { result: 'ok', errors: [], data: { id: '123', price: '100.0', quotes: ['1.0', '2.0'], other: [{id: '124', price: '17.0'}] }, count: 10 }
response.deep_merge!(response, &transform)
这输出:
#=>{:result=>"ok", :errors=>[], :data=>{:id=>"123", :price=>0.1e3, :quotes=>[0.1e1, 0.2e1], :other=>[{:id=>"124", :price=>0.17e2}]}, :count=>10}
我只是用普通的旧 Ruby 和一些递归实现必要的转换逻辑,不需要外部依赖。例如:
def transform(hash, ignore_keys: [])
hash.each_with_object({}) do |(key, value), result|
if value.is_a?(Hash)
result[key] = transform(value, ignore_keys: ignore_keys)
elsif ignore_keys.include?(key.to_s)
result[key] = value
elsif value.to_s =~ /\A[+-]?\d+(\.\d+)?\z/
result[key] = BigDecimal(value)
else
result[key] = value
end
end
end
keys_not_to_transform = %w[id count]
response = { result: 'ok', errors: [], data: { id: '123', price: '100.0' }, count: 10 }
transform(response, ignore_keys: keys_not_to_transform)
# => {:result=>"ok", :errors=>[], :data=>{:id=>"123", :price=>#<BigDecimal:5566613bb128,'0.1E3',9(18)>}, :count=>10}