Daniel Jacob Archer Daniel Jacob Archer - 3 months ago 17
Ruby Question

Ruby transform hash based on mapping

Let's say I have a mapping of how I want a hash to turn out, along with new key names like this:

JSON_MAP = {
image: {
id: :id,
media_url: :url,
time: :duration,
timestamp: :time_posted,
text_caption: :caption,
metadata: {
camera: :camera_type,
flash: :camera_flash
}
},
viewers: {
views: :view_count,
likes: :likes_count
}
}


and I have a hash like this:

{
image: {
id: 1,
media_url: 'http://placekitten.com',
nsfw: false,
time: 4,
timestamp: 14149292,
text_caption: "I'm a kitten",
metadata: {
camera: 'iPhone',
flash: true
}
},
viewers: {
views: 50,
likes: 15
},
extras: {
features: {
enabled: true
}
}
}


I only want it to transform the data so it ends up like:

{
image: {
id: 1,
url: 'http://placekitten.com',
duration: 4,
time_posted: 14149292,
caption: "I'm a kitten",
metadata: {
camera: 'iPhone',
flash: true
}
},
viewers: {
view_count: 50,
likes_count: 15
}
}


Basically, renaming all the keys based on the source map, and deleting any keys that don't match the source map...

Answer

You can obtain your desired result using recursion.

def convert(mapper, hsh)
  mapper.each_with_object({}) do |(k,o),h|
    if o.is_a? Hash
      h[k] = convert(o, hsh[k])
    else
      h[o] = hsh[k]
    end
  end
end

Assuming h equals your second hash,

convert(JSON_MAP, h)    
  #=> { :image=>{
  #       :id=>1,
  #       :url=>"http://placekitten.com",
  #       :duration=>4,
  #       :time_posted=>14149292,
  #       :caption=>"I'm a kitten",
  #       :metadata=>{
  #         :camera_type=>"iPhone",
  #         :camera_flash=>true
  #       }
  #     },
  #     :viewers=>{
  #       :view_count=>50,
  #       :likes_count=>15
  #     }
  #   }