require "hashie_mappy/version" require "active_support/core_ext/hash" module HashieMappy attr_accessor :target_map, :output, :after ALLOWED_TYPES = [Integer, String, Float] def self.extended(base) base.class_eval do self.target_map = nil end end def map(input_map) self.target_map = input_map end def after_normalize(&block) self.after = block end def normalize(input_hash) input_hash = convert_to_hash(input_hash) unless input_hash.is_a?(Hash) input_hash.deep_stringify_keys! output = normalize_hash(self.target_map, input_hash) if input_hash.keys.any? output = after.call(input_hash, output) if after output.with_indifferent_access end def convert_to_hash(input) hash = input.instance_variables.each_with_object({}) do |var, obj| if ALLOWED_TYPES.include?(input.instance_variable_get(var).class) obj[var.to_s.delete('@')] = input.instance_variable_get(var) else obj[var.to_s.delete('@')] = convert_to_hash(input.instance_variable_get(var)) end end hash end def normalize_hash(target_map, input, buried_keys = [], output = {}) target_map.keys.each do |key| if key.include?('*') keys = key.split('*') handle_array(input[keys[1]], target_map[key], keys[0], output) if input.is_a?(Hash) begin handle_array(input.send(keys[1]), target_map[key], keys[0], output) rescue NoMethodError nil end else buried_keys.push(key) if target_map[key].is_a?(Hash) normalize_hash(target_map[key], input, buried_keys, output) else output.bury(buried_keys, find_input_value(input, target_map[key])) end buried_keys = [] end end output end def handle_array(input, target_map, key, output) output[key] = input.map {|i| normalize_hash(target_map, i)} rescue NoMethodError nil end def find_input_value(obj, key) return recurse_dig(key.split('#'), obj) if key.split('#').count > 1 if obj.respond_to?(:key?) && obj.key?(key) obj[key] elsif obj.respond_to?(:key?) && obj.key?(key.to_sym) obj[key.to_sym] elsif obj.respond_to?(:each) r = nil obj.find{ |*a| r = find_input_value(a.last, key) } r else begin obj.send(key) rescue NoMethodError nil end end end def recurse_dig(keys, obj) if keys.count > 1 key = keys.shift return recurse_dig(keys, obj.dig(key)) if obj.is_a?(Hash) recurse_dig(keys, obj.send(key)) else return obj.dig(keys.shift) end rescue NoMethodError nil end end class Hash def bury(keys, value) if keys.count < 1 raise ArgumentError.new("2 or more arguments required") elsif keys.count == 1 self[keys[0]] = value else key = keys.shift self[key] = {} unless self[key] self[key].bury(keys, value) unless keys.empty? end self end end