23tux 23tux - 1 year ago 93
Ruby Question

Flatten deep nested hash to array for sha1 hashing

I want to compute an unique sha1 hash from a ruby hash. I thought about

  • (Deep) Converting the Hash into an array

  • Sorting the array

  • Join array by empty string

  • calculate sha1

Consider the following hash:

hash = {
foo: "test",
bar: [1,2,3]
hello: {
world: "world",
arrays: [
{foo: "bar"}

How can I get this kind of nested hash into an array like

[:foo, "test", :bar, 1, 2, 3, :hello, :world, "earth", :arrays, :my, "example"]

I would then sort the array, join it with
and compute the sha1 hash like this:

require 'digest/sha1'
Digest::SHA1.hexdigest hash_string

  1. How could I flatten the hash like I described above?

  2. Is there already a gem for this?

  3. Is there a quicker / easier way to solve this? I have a large amount of objects to convert (~700k), so performance does matter.


Another problem that I figured out by the answers below are this two hashes:

a = {a: "a", b: "b"}
b = {a: "b", b: "a"}

When flattening the hash and sorting it, this two hashes produce the same output, even when
a == b => false


The use case for this whole thing is product data comparison. The product data is stored inside a hash, then serialized and sent to a service that creates / updates the product data.

I want to check if anything has changed inside the product data, so I generate a hash from the product content and store it in a database. The next time the same product is loaded, I calculate the hash again, compare it to the one in the DB and decide wether the product needs an update or not.

Answer Source

EDIT : As you detailed, two hashes with keys in different order should give the same string. I would reopen the Hash class to add my new custom flatten method :

class Hash
  def fully_flatten()
    self.sort.flatten.map{ |elem| elem.is_a?(Hash) ? elem.fully_flatten : elem }.flatten

Explanation :

  • sort converts the hash to a sorted array of pairs (for the comparison of hashes with different keys order)
  • flatten converts an array of arrays into a single array of values
  • map{ |elem| elem.is_a?(Hash) ? elem.fully_flatten : elem } calls back fully_flatten on any sub-hash left.

Then you just need to use :

require 'digest/sha1'
Digest::SHA1.hexdigest hash.fully_flatten.to_s