module Dry::Transformer::HashTransformations
Transformation functions for Hash objects
@example
require 'dry/transformer/hash' include Dry::Transformer::Helper fn = t(:symbolize_keys) >> t(:nest, :address, [:street, :zipcode]) fn["street" => "Street 1", "zipcode" => "123"] # => {:address => {:street => "Street 1", :zipcode => "123"}}
@api public
Public Class Methods
Accepts specified keys from a hash
@example
Dry::Transformer(:accept_keys, [:name])[name: 'Jane', email: 'jane@doe.org'] # => {:name=>"Jane"}
@param [Hash] hash The input hash @param [Array] keys The keys to be accepted
@return [Hash]
@api public
# File lib/dry/transformer/hash.rb, line 227 def self.accept_keys(hash, keys) Hash[hash].slice(*keys) end
Copy all keys in a hash using provided mapping hash
@example
Dry::Transformer(:copy_keys, user_name: :name)[user_name: 'Jane'] # => {:user_name => "Jane", :name => "Jane"}
@param [Hash] source_hash The input hash @param [Hash] mapping The key-copy mapping
@return [Hash]
@api public
# File lib/dry/transformer/hash.rb, line 188 def self.copy_keys(source_hash, mapping) Hash[source_hash].tap do |hash| mapping.each do |original_key, new_keys| [*new_keys].each do |new_key| hash[new_key] = hash[original_key] end end end end
Merge a hash recursively
@example
input = { 'foo' => 'bar', 'baz' => { 'one' => 1 } } other = { 'foo' => 'buz', 'baz' => { :one => 'one', :two => 2 } } t(:deep_merge)[input, other] # => { 'foo' => "buz", :baz => { :one => 'one', 'one' => 1, :two => 2 } }
@param [Hash] @param [Hash]
@return [Hash]
@api public
# File lib/dry/transformer/hash.rb, line 442 def self.deep_merge(hash, other) Hash[hash].merge(other) do |_, original_value, new_value| if original_value.respond_to?(:to_hash) && new_value.respond_to?(:to_hash) deep_merge(Hash[original_value], Hash[new_value]) else new_value end end end
Stringify keys in a hash recursively
@example
input = { :foo => "bar", :baz => [{ :one => 1 }] } t(:deep_stringify_keys)[input] # => { "foo" => "bar", "baz" => [{ "one" => 1 }] }
@param [Hash]
@return [Hash]
@api public
# File lib/dry/transformer/hash.rb, line 119 def self.deep_stringify_keys(hash) hash.each_with_object({}) do |(key, value), output| output[key.to_s] = case value when Hash deep_stringify_keys(value) when Array value.map { |item| item.is_a?(Hash) ? deep_stringify_keys(item) : item } else value end end end
Symbolize keys in a hash recursively
@example
input = { 'foo' => 'bar', 'baz' => [{ 'one' => 1 }] } t(:deep_symbolize_keys)[input] # => { :foo => "bar", :baz => [{ :one => 1 }] }
@param [Hash]
@return [Hash]
@api public
# File lib/dry/transformer/hash.rb, line 75 def self.deep_symbolize_keys(hash) hash.each_with_object({}) do |(key, value), output| output[key.to_sym] = case value when Hash deep_symbolize_keys(value) when Array value.map { |item| item.is_a?(Hash) ? deep_symbolize_keys(item) : item } else value end end end
Recursively evaluate hash values if they are procs/lambdas
@example
hash = { num: -> i { i + 1 }, str: -> i { "num #{i}" } } t(:eval_values, 1)[hash] # => {:num => 2, :str => "num 1" } # with filters t(:eval_values, 1, [:str])[hash] # => {:num => #{still a proc}, :str => "num 1" }
@param [Hash] @param [Array,Object] args Anything that should be passed to procs @param [Array] filters A list of attribute names that should be evaluated
@api public
# File lib/dry/transformer/hash.rb, line 404 def self.eval_values(hash, args, filters = []) hash.each_with_object({}) do |(key, value), output| output[key] = case value when Proc if filters.empty? || filters.include?(key) value.call(*args) else value end when Hash eval_values(value, args, filters) when Array value.map { |item| item.is_a?(Hash) ? eval_values(item, args, filters) : item } else value end end end
Folds array of tuples to array of values from a specified key
@example
source = { name: "Jane", tasks: [{ title: "be nice", priority: 1 }, { title: "sleep well" }] } Dry::Transformer(:fold, :tasks, :title)[source] # => { name: "Jane", tasks: ["be nice", "sleep well"] } Dry::Transformer(:fold, :tasks, :priority)[source] # => { name: "Jane", tasks: [1, nil] }
@param [Hash] hash @param [Object] key The key to fold values to @param [Object] tuple_key The key to take folded values from
@return [Hash]
@api public
# File lib/dry/transformer/hash.rb, line 339 def self.fold(hash, key, tuple_key) hash.merge(key => ArrayTransformations.extract_key(hash[key], tuple_key)) end
Map all keys in a hash with the provided transformation function
@example
Dry::Transformer(:map_keys, -> s { s.upcase })['name' => 'Jane'] # => {"NAME" => "Jane"}
@param [Hash]
@return [Hash]
@api public
# File lib/dry/transformer/hash.rb, line 35 def self.map_keys(source_hash, fn) Hash[source_hash].transform_keys!(&fn) end
Map a key in a hash with the provided transformation function
@example
Dry::Transformer(:map_value, 'name', -> s { s.upcase })['name' => 'jane'] # => {"name" => "JANE"}
@param [Hash]
@return [Hash]
@api public
# File lib/dry/transformer/hash.rb, line 247 def self.map_value(hash, key, fn) hash.merge(key => fn[hash[key]]) end
Map all values in a hash using transformation function
@example
Dry::Transformer(:map_values, -> v { v.upcase })[:name => 'Jane'] # => {"name" => "JANE"}
@param [Hash]
@return [Hash]
@api public
# File lib/dry/transformer/hash.rb, line 147 def self.map_values(source_hash, fn) Hash[source_hash].transform_values!(&fn) end
Nest values from specified keys under a new key
@example
Dry::Transformer(:nest, :address, [:street, :zipcode])[street: 'Street', zipcode: '123'] # => {address: {street: "Street", zipcode: "123"}}
@param [Hash]
@return [Hash]
@api public
# File lib/dry/transformer/hash.rb, line 262 def self.nest(hash, root, keys) child = {} keys.each do |key| child[key] = hash[key] if hash.key?(key) end output = Hash[hash] child.each_key { |key| output.delete(key) } old_root = hash[root] if old_root.is_a?(Hash) output[root] = old_root.merge(child) else output[root] = child end output end
Rejects specified keys from a hash
@example
Dry::Transformer(:reject_keys, [:name])[name: 'Jane', email: 'jane@doe.org'] # => {:email => "jane@doe.org"}
@param [Hash] hash The input hash @param [Array] keys The keys to be rejected
@return [Hash]
@api public
# File lib/dry/transformer/hash.rb, line 210 def self.reject_keys(hash, keys) Hash[hash].reject { |k, _| keys.include?(k) } end
Rename all keys in a hash using provided mapping hash
@example
Dry::Transformer(:rename_keys, user_name: :name)[user_name: 'Jane'] # => {:name => "Jane"}
@param [Hash] source_hash The input hash @param [Hash] mapping The key-rename mapping
@return [Hash]
@api public
# File lib/dry/transformer/hash.rb, line 170 def self.rename_keys(source_hash, mapping) Hash[source_hash].tap do |hash| mapping.each { |k, v| hash[v] = hash.delete(k) if hash.key?(k) } end end
Splits hash to array by all values from a specified key
The operation adds missing keys extracted from the array to regularize the output.
@example
input = { name: 'Joe', tasks: [ { title: 'sleep well', priority: 1 }, { title: 'be nice', priority: 2 }, { priority: 2 }, { title: 'be cool' } ] } Dry::Transformer(:split, :tasks, [:priority])[input] => [ { name: 'Joe', priority: 1, tasks: [{ title: 'sleep well' }] }, { name: 'Joe', priority: 2, tasks: [{ title: 'be nice' }, { title: nil }] }, { name: 'Joe', priority: nil, tasks: [{ title: 'be cool' }] } ]
@param [Hash] hash @param [Object] key The key to split a hash by @param [Array] subkeys The list of subkeys to be extracted from key
@return [Array<Hash>]
@api public
# File lib/dry/transformer/hash.rb, line 371 def self.split(hash, key, keys) list = Array(hash[key]) return [hash.reject { |k, _| k == key }] if list.empty? existing = list.flat_map(&:keys).uniq grouped = existing - keys ungrouped = existing & keys list = ArrayTransformations.group(list, key, grouped) if grouped.any? list = list.map { |item| item.merge(reject_keys(hash, [key])) } ArrayTransformations.add_keys(list, ungrouped) end
Stringify all keys in a hash
@example
Dry::Transformer(:stringify_keys)[:name => 'Jane'] # => {"name" => "Jane"}
@param [Hash]
@return [Hash]
@api public
# File lib/dry/transformer/hash.rb, line 102 def self.stringify_keys(hash) map_keys(hash, Coercions[:to_string].fn) end
Symbolize all keys in a hash
@example
Dry::Transformer(:symbolize_keys)['name' => 'Jane'] # => {:name => "Jane"}
@param [Hash]
@return [Hash]
@api public
# File lib/dry/transformer/hash.rb, line 57 def self.symbolize_keys(hash) map_keys(hash, Coercions[:to_symbol].fn) end
Collapse a nested hash from a specified key
@example
Dry::Transformer(:unwrap, :address, [:street, :zipcode])[address: { street: 'Street', zipcode: '123' }] # => {street: "Street", zipcode: "123"}
@param [Hash] source_hash @param [Mixed] root The root key to unwrap values from @param [Array] selected The keys that should be unwrapped (optional) @param [Hash] options hash of options (optional) @option options [Boolean] :prefix if true, unwrapped keys will be prefixed
with the root key followed by an underscore (_)
@return [Hash]
@api public
# File lib/dry/transformer/hash.rb, line 300 def self.unwrap(source_hash, root, selected = nil, prefix: false) return source_hash unless source_hash[root] add_prefix = lambda do |key| combined = [root, key].join('_') root.is_a?(::Symbol) ? combined.to_sym : combined end Hash[source_hash].merge(root => Hash[source_hash[root]]).tap do |hash| nested_hash = hash[root] keys = nested_hash.keys keys &= selected if selected new_keys = prefix ? keys.map(&add_prefix) : keys nested_contains_root_key = nested_hash.key?(root) hash.update(Hash[new_keys.zip(keys.map { |key| nested_hash.delete(key) })]) hash.delete(root) if nested_hash.empty? && !nested_contains_root_key end end