class HashRemapper

Utility class to map original Hash keys to the new ones

Constants

VERSION

Current Library Version

Public Class Methods

remap(data, pass_trough = false, mapping) click to toggle source

Remaps `data` Hash by renaming keys, creating new ones and optionally aggregating values

@example

HashRemapper.remap(
  {a: 1, b: 2, c: 3},
  true
  a: :one,
  b: :two
) # => { one: 1, two: 2, c: 3 }

@param [Hash] data the original Hash to remap @param [Boolean] pass_trough the flag to pass the original key/value pairs (default: false) @param [Hash] mapping the Hash which in the simplest case tells how to rename keys (source: :target)

@return [Hash] remapped version of the original Hash

(selected keys only or all the keys if we passed originals)
# File lib/hash_remapper.rb, line 29
def remap(data, pass_trough = false, mapping)
  mapping = pass_trough_mapping(data, mapping) if pass_trough

  mapping.each_with_object(Hash.new { |hash, key| hash[key] = Hash.new(&hash.default_proc) }) do |(from, to), acc|
    key, value = try_callable(from, to, data, acc) ||
                 try_digging(from, to, data, acc) ||
                 try_nesting(from, to, data, acc) ||
                 [to, data[from]]

    set_value(acc, key, value)
    acc
  end
end

Private Class Methods

pass_trough_mapping(data, mapping) click to toggle source

Method which automatically prepares direct mapping (e.g. { :a => :a }) for the keys that weren't used in the mapping Hash (to pass them through “as is”)

@param [Hash] data the whole original Hash to take keys from @param [Hash] mapping the mapping to use as the reference of the used keys

@return [Hash] new mapping Hash containing original mapping + direct mappings

# File lib/hash_remapper.rb, line 116
def pass_trough_mapping(data, mapping)
  original_keys = Set.new(data.keys)
  mapping_keys  = Set.new(mapping.keys)

  pass_trough_keys    = original_keys.difference(mapping_keys)
  pass_trough_mapping = Hash[pass_trough_keys.zip(pass_trough_keys)]

  mapping.merge(pass_trough_mapping)
end
set_value(data, key, value) click to toggle source
# File lib/hash_remapper.rb, line 126
def set_value(data, key, value)
  unless key.respond_to?(:each)
    data[key] = value
    return data
  end

  last_key = key.pop

  data.dig(*key)[last_key] = value
end
try_callable(from, to, data, acc) click to toggle source

Method to try to handle callable mapping Hash value (if the mapping value is callable)

@param [Object] from the source key to handle @param [Object] to the target key to map to @param [Hash] data the whole original Hash to use as the context in the lambda @param [Hash] acc the accumulated result hash to check and merge existed data

@return [Array(Object,Object)] key and its value to put to the resulting Hash

# File lib/hash_remapper.rb, line 54
def try_callable(from, to, data, acc)
  return unless to.respond_to?(:call)

  target_name, target_data = to.call(data.dig(*from), data)

  target_data = acc.dig(*target_name).respond_to?(:merge) && target_data.respond_to?(:merge) ?
                  acc.dig(*target_name).merge(target_data) :
                  target_data

  [target_name, target_data]
end
try_digging(_from, to, data, acc) click to toggle source

Method to try to handle data digging (if the mapping value is enumerable) @see github.com/smileart/hash_digger

@param [Array] to the target key to map to ([new_key, {path: 'a.*.b', strict: false, lambda: ->(res){ … }}]) @param [Hash] data the whole original Hash to use as the digging target

@return [Array(Object,Object)] key and its value to put to the resulting Hash

# File lib/hash_remapper.rb, line 74
def try_digging(_from, to, data, acc)
  return unless to.respond_to?(:each)

  digger_args = to.fetch(1)

  # v0.1.0 backward compartability layer ([new_key, [:digg, :path, :keys]])
  return [to.first, data.dig(*to.last)] if digger_args.kind_of?(Array)
  return unless digger_args.respond_to?(:fetch)

  lambda = digger_args.fetch(:lambda) { nil }

  # @see https://github.com/DamirSvrtan/fasterer — fetch_with_argument_vs_block
  # @see https://github.com/smileart/hash_digger — digger args
  target_name = to.fetch(0)

  source_data = HashDigger::Digger.dig(
    data: data,
    path: digger_args.fetch(:path) { '*' },
    strict: digger_args.fetch(:strict) { true },
    default: digger_args.fetch(:default) { nil },
    &lambda
  )

  return [
    target_name,
    acc.dig(*target_name).respond_to?(:merge) && source_data.respond_to?(:merge) ?
      acc.dig(*target_name).merge(source_data) :
      source_data
  ]
end
try_nesting(_from, to, data, _acc) click to toggle source
# File lib/hash_remapper.rb, line 105
def try_nesting(_from, to, data, _acc)
  [to.fetch(0), data[to.fetch(1)]] if to.respond_to?(:fetch)
end