Manipulate hash in Ruby - ruby

I have a hash that looks like
{
"lt"=>"456",
"c"=>"123",
"system"=>{"pl"=>"valid-player-name", "plv"=>"player_version_1"},
"usage"=>{"trace"=>"1", "cq"=>"versionid", "stream"=>"od",
"uid"=>"9", "pst"=>[["0", "1", "10"]], "dur"=>"0", "vt"=>"2"}
}
How can I go about turning it into a hash that looks like
{
"lt"=>"456",
"c"=>"123",
"pl"=>"valid-player-name",
"plv"=>"player_version_1",
"trace"=>"1",
"cq"=>"versionid",
"stream"=>"od",
"uid"=>"9",
"pst"=>[["0", "1", "10"]], "dur"=>"0", "vt"=>"2"
}
I basically want to get rid of the keys system and usage and keep what's nested inside them

"Low-tech" version :)
h = { ... }
h.merge!(h.delete('system'))
h.merge!(h.delete('usage'))

Assuming no rails:
hash.reject { |key, _| %w(system usage).include? key }.merge(hash['system']).merge(hash['usage'])
With active support:
hash.except('system', 'usage').merge(hash['system']).merge(hash['usage'])

A more generic version.
Merge any key that contains a hash:
h = { ... }
hnew = h.inject(h.dup) { |h2, (k, v)|
h2.merge!(h2.delete(k)) if v.is_a?(Hash)
h2
}

Assuming that your data has the same structure each time, I might opt for something simple and easy to understand like this:
def manipulate_hash(h)
{
"lt" => h["lt"],
"c" => h["c"],
"pl" => h["system"]["pl"],
"plv" => h["system"]["plv"],
"trace" => h["usage"]["trace"],
"cq" => h["usage"]["cq"],
"stream" => h["usage"]["stream"],
"uid" => h["uid"],
"pst" => h["pst"],
"dur" => h["dur"],
"vt" => h["vt"]
}
end
I chose to make the hash using one big hash literal expression that spans multiple lines. If you don't like that, you could build it up on multiple lines like this:
def manipulate_hash
r = {}
r["lt"] = h["lt"]
r["c"] = h["c"]
...
r
end
You might consider using fetch instead of the [] angle brackets. That way, you'll get an exception if the expected key is missing from the hash. For example, replace h["lt"] with h.fetch("lt").

If you plan to have an arbitrarily large list of keys to merge, this is an easily scaleable method:
["system", "usage"].each_with_object(myhash) do |key|
myhash.merge!(myhash.delete(key))
end

Related

Get the key of a hash by knowing only one item inside the value which is an array in Ruby

I want to be able to get the key called "needed key" as a string out of this considering I just the value of "known_value
hash = {
"needed key" => {
:key1 => ["know_value", "unknown_value"],
:key2 => ["stuff", "more_stuff"]
}
"otherhash" => {
:key1 => ["unknow_value", "unknown_value"],
:key2 => []
}
}
In short how would I be able to retreive "needed key" from this code if I only know "know_value". So by simply using "know_value" out of this I need to get "needed key".
Thank you very much for your help.
Hopefully I am clear enough, otherwise please ask me to be clearer.
hash.select{|k, v| v.values.flatten.include?('known_value')}.keys
This is a way to extract the desired key that is efficient in the sense that it avoids the construction of temporary, intermediate arrays, such as hash.keys, flatten, hash[key], and so on.
target = "known_value"
hash.each_key.find { |k| hash[k].each_value { |a| a.include?(target) } }
#=> "needed key"
This assumes, of course, that we are looking for any key that satisfies the requirement.
If you only care about one of them then you could use find:
hash.find { |k, h| h.values.flatten.include?('know_value') }.first
or if you want to avoid flatten, you could use any? on the inner arrays:
hash.find { |k,h| h.values.any? { |a| a.include?('know_value') } }.first
Not a Ruby expert but I think this gets the job done.
hash.keys.select{ |key|
subhash = hash[key]
subhash.keys.any? { |subkey|
subhash[subkey].include? 'known_value'
}
}.first

Is there a built-in Ruby method for "reshaping" a hash?

I have this hash which I retrieve from a database:
original_hash = {
:name => "Luka",
:school => {
:id => "123",
:name => "Ieperman"
},
:testScores => [0.8, 0.5, 0.4, 0.9]
}
I'm writing an API and want to return a slightly different hash to the client:
result = {
:name => "Luka",
:schoolName => "Ieperman",
:averageScore => 0.65
}
This doesn't work because the method reshape doesn't exist. Does it exist by another name though?
result = original_hash.reshape do |hash|
{
:name => hash[:name],
:school => hash[:school][:name],
:averageScore => hash[:testScores].reduce(:+).to_f / hash[:testScores].count
}
end
I'm new to Ruby so thought I'd ask before I go off overriding core classes. I'm sure it must exist as I always find myself reshaping hashes when writing an API. Or am I totally missing something?
The implementation is dead simple but, like I said, I don't want to override Hash if I don't need to:
class Hash
def reshape
yield(self)
end
end
BTW, I know about this:
result = {
:name => original_hash[:name],
:school => original_hash[:school][:name],
:averageScore => original_hash[:testScores].reduce(:+).to_f / original_hash[:testScores].count
}
But sometimes I don't have an original_hash variable and instead I'm operating straight off a return value, or I'm inside a one liner where this block based approach would be convenient.
Real World example:
#get the relevant user settings from the database, and reshape the hash into the form we want
settings = users.find_one({:_id => oid(a[:userID])}, {:emailNotifications => 1, :newsletter => 1, :defaultSocialNetwork => 1}).reshape do |hash|
{
:emailNotifications => hash[:emailNotifications] == 1,
:newsletter => hash[:newsletter] == 1,
:defaultSocialNetwork => hash[:defaultSocialNetwork]
}
end rescue fail
If you're using Ruby >= 1.9, try a combination of Object#tap and Hash#replace
def foo(); { foo: "bar" }; end
foo().tap { |h| h.replace({original_foo: h[:foo]}) }
# => { :original_foo => "bar" }
Since Hash#replace works in-place, you might find this a bit safer:
foo().clone.tap { |h| h.replace({original_foo: h[:foo]}) }
But this is getting a bit noisy. I'd probably go ahead and monkey-patch Hash at this stage.
From an API perspective, you may be looking for a representer object to sit between your internal model, and the API representation (prior to format-based serialisation). This doesn't work using the shortest, convenient Ruby syntax inline for a hash, but is a nice declarative approach.
For instance, the Grape gem (other API frameworks are available!) might solve the same real-world problem as:
# In a route
get :user_settings do
settings = users.find_one({:_id => oid(a[:userID])}, {:emailNotifications => 1, :newsletter => 1, :defaultSocialNetwork => 1})
present settings, :with => SettingsEntity
end
# Wherever you define your entities:
class SettingsEntity < Grape::Entity
expose( :emailNotifications ) { |hash,options| hash[:emailNotifications] == 1 }
expose( :newsletter ) { |hash,options| hash[:newsletter] == 1 }
expose( :defaultSocialNetwork ) { |hash,options| hash[:defaultSocialNetwork] }
end
This syntax is more geared towards handling ActiveRecord, or similar models, and not hashes though. So not a direct answer to your question, but I think implied by you building up an API. If you put in a representer layer of some kind now (not necessarily grape-entity), you will be thankful for it later, as you'll be better able to manage your model-to-API data mappings when they need to change.
You can replace the call to "reshape" with the builtin method Object#instance_eval and it will work exactly as such. Note however that there may be some unexpected behavior since you evaluating code in the context of the receiving object (e.g. if using "self").
result = original_hash.instance_eval do |hash|
# ...
This abstraction does not exist in the core but people uses it (with different names, pipe, into, as, peg, chain, ...). Note that this let-abstraction is useful not only for hashes, so add it to the class Object.
Is there a `pipe` equivalent in ruby?
if you put your hashes in a array you could use the map function to convert the entries
I can't think of anything that will do this magically, since you're essentially wanting to remap an arbitrary data structure.
Something you may be able to do is:
require 'pp'
original_hash = {
:name=>'abc',
:school => {
:name=>'school name'
},
:testScores => [1,2,3,4,5]
}
result = {}
original_hash.each {|k,v| v.is_a?(Hash) ? v.each {|k1,v1| result[ [k.to_s, k1.to_s].join('_') ] = v1 } : result[k] = v}
result # {:name=>"abc", "school_name"=>"school name", :testScores=>[1, 2, 3, 4, 5]}
but this is incredibly messy and I'd personally be unhappy with it. Performing a manual transform on known keys is probaby better and more maintainable.
Check Facets Hash extensions.

Apply an array containg keys iterative to a hash

I have the following array
["key", "key_deeper", "key_even_deeper"]
and a hash:
{ "key" => { "key_deeper" => { "key_even_deeper" => "BINGO!" } } }
What is the shortest or most expressive way to apply the array on the hash to receive "BINGO!"?
That is for the base case, but there is also a special case where the value to a key is not only String => Hash, but also String => [Integer, Hash].
For instance
["key1", "key2"]
on Hash
{"key1" => [5, {"key2" => "BINGO!" }] }
should return again "BINGO!", but an array containing only ["key1"] would simply return 5.
Probably the easiest way is to use inject:
array.inject(hash) do |h, i|
h.fetch(i){ {} }
end
# => "BINGO!"
The fetch is used to prevent a NoMethodError in case one of your lookup values is not present in the hash. However, in that case, it will return an empty hash. You may want to do the standard lookup instead, i.e.
array.inject(hash) {|h,i| h[i] }
Edit:
Here's an even shorter way to do this (I don't know if I would say it's 'more expressive', but it is shorter):
array.inject(hash, :[])
You can change the original answer a little bit for your second version of question:
array.inject(hash){ |h,i| h[i].is_a?(Array) ? h[i].last : h[i] }

Ruby : Generate a Hash of Hashes from an Array of Hashes

I have the following
friends = [{ name: "Jack", attr1:"def", attr2:"def" }, { name: "Jill", attr1:"def", attr2:"def" }]
I want to convert the above representation into a hash of hashes like this
friends = { "Jack" => { attr1: "def", attr2:"def" }, "Jill" => { attr1: "def", attr2: "def" } }
Any elegant way of doing this in Ruby ?
Hash[friends.map { |f| _f = f.dup; [_f.delete(:name), _f] }]
# => {"Jack"=>{:attr1=>"def", :attr2=>"def"}, "Jill"=>{:attr1=>"def", :attr2=>"def"}}
friends.each_with_object({}) do |f, o|
f = f.dup
o[f.delete :name] = f
end
hash = {}
friends.each{|h| hash[h.delete(:name)] = h }
# => {"Jack"=>{:attr1=>"def", :attr2=>"def"}, "Jill"=>{:attr1=>"def", :attr2=>"def"}}
When you want to transform one array into another, use collect:
friends = Hash[
friends.collect do |f|
_f = f.dup
name = _f.delete(:name)
[ name, _f ]
end
]
You can create a new hash easily using Hash[] and provide it an array with a series of key/value pairs in it. In this case the name field is removed from each.
If we understand "elegant" as the way to write concise code by leveraging reusable abstractions, I'd write:
require 'active_support/core_ext/hash'
require 'facets/hash'
friends.mash { |f| [f[:name], f.except(:name)] }
No need to add gem dependencies for these two fairly big libraries, you can always implement the individual methods in your extensions library.

Converting nested hash keys from CamelCase to snake_case in Ruby

I'm trying to build an API wrapper gem, and having issues with converting hash keys to a more Rubyish format from the JSON the API returns.
The JSON contains multiple layers of nesting, both Hashes and Arrays. What I want to do is to recursively convert all keys to snake_case for easier use.
Here's what I've got so far:
def convert_hash_keys(value)
return value if (not value.is_a?(Array) and not value.is_a?(Hash))
result = value.inject({}) do |new, (key, value)|
new[to_snake_case(key.to_s).to_sym] = convert_hash_keys(value)
new
end
result
end
The above calls this method to convert strings to snake_case:
def to_snake_case(string)
string.gsub(/::/, '/').
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
gsub(/([a-z\d])([A-Z])/,'\1_\2').
tr("-", "_").
downcase
end
Ideally, the result would be similar to the following:
hash = {:HashKey => {:NestedHashKey => [{:Key => "value"}]}}
convert_hash_keys(hash)
# => {:hash_key => {:nested_hash_key => [{:key => "value"}]}}
I'm getting the recursion wrong, and every version of this sort of solution I've tried either doesn't convert symbols beyond the first level, or goes overboard and tries to convert the entire hash, including values.
Trying to solve all this in a helper class, rather than modifying the actual Hash and String functions, if possible.
Thank you in advance.
If you use Rails:
Example with hash: camelCase to snake_case:
hash = { camelCase: 'value1', changeMe: 'value2' }
hash.transform_keys { |key| key.to_s.underscore }
# => { "camel_case" => "value1", "change_me" => "value2" }
source:
http://apidock.com/rails/v4.0.2/Hash/transform_keys
For nested attributes use deep_transform_keys instead of transform_keys, example:
hash = { camelCase: 'value1', changeMe: { hereToo: { andMe: 'thanks' } } }
hash.deep_transform_keys { |key| key.to_s.underscore }
# => {"camel_case"=>"value1", "change_me"=>{"here_too"=>{"and_me"=>"thanks"}}}
source: http://apidock.com/rails/v4.2.7/Hash/deep_transform_keys
You need to treat Array and Hash separately. And, if you're in Rails, you can use underscore instead of your homebrew to_snake_case. First a little helper to reduce the noise:
def underscore_key(k)
k.to_s.underscore.to_sym
# Or, if you're not in Rails:
# to_snake_case(k.to_s).to_sym
end
If your Hashes will have keys that aren't Symbols or Strings then you can modify underscore_key appropriately.
If you have an Array, then you just want to recursively apply convert_hash_keys to each element of the Array; if you have a Hash, you want to fix the keys with underscore_key and apply convert_hash_keys to each of the values; if you have something else then you want to pass it through untouched:
def convert_hash_keys(value)
case value
when Array
value.map { |v| convert_hash_keys(v) }
# or `value.map(&method(:convert_hash_keys))`
when Hash
Hash[value.map { |k, v| [underscore_key(k), convert_hash_keys(v)] }]
else
value
end
end
I use this short form:
hash.transform_keys(&:underscore)
And, as #Shanaka Kuruwita pointed out, to deeply transform all the nested hashes:
hash.deep_transform_keys(&:underscore)
The accepted answer by 'mu is too short' has been converted into a gem, futurechimp's Plissken:
https://github.com/futurechimp/plissken/blob/master/lib/plissken/ext/hash/to_snake_keys.rb
This looks like it should work outside of Rails as the underscore functionality is included.
Use deep_transform_keys for recursive conversion.
transform_keys only convert it in high level
hash = { camelCase: 'value1', changeMe: {nestedMe: 'value2'} }
hash.transform_keys { |key| key.to_s.underscore }
# => { "camel_case" => "value1", "change_me" => {nestedMe: 'value2'} }
deep_transform_keys will go deeper and transform all nested hashes as well.
hash = { camelCase: 'value1', changeMe: {nestedMe: 'value2'} }
hash.deep_transform_keys { |key| key.to_s.underscore }
# => { "camel_case" => "value1", "change_me" => {nested_me: 'value2'} }
If you're using the active_support library, you can use deep_transform_keys! like so:
hash.deep_transform_keys! do |key|
k = key.to_s.snakecase rescue key
k.to_sym rescue key
end
This works both to camelCase and snake_case deep nested keys of an object, which is very useful for a JSON API:
def camelize_keys(object)
deep_transform_keys_in_object!(object) { |key| key.to_s.camelize(:lower) }
end
def snakecase_keys(object)
deep_transform_keys_in_object!(object) { |key| key.to_s.underscore.to_sym }
end
def deep_transform_keys_in_object!(object, &block)
case object
when Hash
object.keys.each do |key|
value = object.delete(key)
object[yield(key)] = deep_transform_keys_in_object!(value, &block)
end
object
when Array
object.map! { |e| deep_transform_keys_in_object!(e, &block) }
else
object
end
end

Resources