Skip to content

Instantly share code, notes, and snippets.

@rdeusser
Forked from morhekil/LICENSE
Last active January 22, 2017 22:12
Show Gist options
  • Save rdeusser/6a6e49bf2180ea253aa656d9def86172 to your computer and use it in GitHub Desktop.
Save rdeusser/6a6e49bf2180ea253aa656d9def86172 to your computer and use it in GitHub Desktop.
Implementation of deep symbolization of keys for Ruby hashes
# Symbolizes all of hash's keys and subkeys.
# Also allows for custom pre-processing of keys (e.g. downcasing, etc)
# if the block is given:
#
# somehash.deep_symbolize { |key| key.downcase }
#
# Usage: either include it into global Hash class to make it available to
# to all hashes, or extend only your own hash objects with this
# module.
# E.g.:
# 1) class Hash; include DeepSymbolizable; end
# 2) myhash.extend DeepSymbolizable
module DeepSymbolizable
def deep_symbolize(&block)
method = self.class.to_s.downcase.to_sym
syms = DeepSymbolizable::Symbolizers
syms.respond_to?(method) ? syms.send(method, self, &block) : self
end
module Symbolizers
extend self
# the primary method - symbolizes keys of the given hash,
# preprocessing them with a block if one was given, and recursively
# going into all nested enumerables
def hash(hash, &block)
hash.inject({}) do |result, (key, value)|
# Recursively deep-symbolize subhashes
value = _recurse_(value, &block)
# Pre-process the key with a block if it was given
key = yield key if block_given?
# Symbolize the key string if it responds to to_sym
sym_key = key.to_sym rescue key
# write it back into the result and return the updated hash
result[sym_key] = value
result
end
end
# walking over arrays and symbolizing all nested elements
def array(ary, &block)
ary.map { |v| _recurse_(v, &block) }
end
# handling recursion - any Enumerable elements (except String)
# is being extended with the module, and then symbolized
def _recurse_(value, &block)
if value.is_a?(Enumerable) && !value.is_a?(String)
# support for a use case without extended core Hash
value.extend DeepSymbolizable unless value.class.include?(DeepSymbolizable)
value = value.deep_symbolize(&block)
end
value
end
end
end
require 'rspec'
require 'deep_symbolize'
describe 'Hash#deep_symbolize' do
let(:hash) {{}}
subject do
hash.extend DeepSymbolizable
hash.deep_symbolize
end
context 'on simple hash' do
let(:hash) {{ :key1 => 'val1', 'key2' => 'val2' }}
it { should == { :key1 => 'val1', :key2 => 'val2' } }
end
context 'on nested hash' do
let(:hash) {{ 'key1' => 'val1', 'subkey' => { 'key2' => 'val2' } }}
it { should == { :key1 => 'val1', :subkey => { :key2 => 'val2' } } }
end
context 'on a hash with nested array' do
let(:hash) {{ 'key1' => 'val1', 'subkey' => [{ 'key2' => 'val2' }] }}
it { should == { :key1 => 'val1', :subkey => [{ :key2 => 'val2' }] } }
end
describe 'preprocessing keys' do
subject do
hash.extend DeepSymbolizable
hash.deep_symbolize { |k| k.upcase }
end
let(:hash) {{ 'key1' => 'val1', 'subkey' => [{ 'key2' => 'val2' }] }}
it { should == { :KEY1 => 'val1', :SUBKEY => [{ :KEY2 => 'val2' }] } }
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment