J'ai besoin de créer une chaîne de signature pour une variable dans Ruby, où la variable peut être un nombre, une chaîne, un hachage ou un tableau. Les valeurs de hachage et les éléments de tableau peuvent également être de n'importe lequel de ces types.
Cette chaîne sera utilisée pour comparer les valeurs dans une base de données (Mongo, dans ce cas).
Ma première pensée a été de créer un hachage MD5 d'une valeur codée JSON, comme ceci: (le corps est la variable mentionnée ci-dessus)
def createsig(body)
Digest::MD5.hexdigest(JSON.generate(body))
end
Cela fonctionne presque, mais JSON.generate n'encode pas les clés d'un hachage dans le même ordre à chaque fois, donc createsig({:a=>'a',:b=>'b'})
n'est pas toujours égal à createsig({:b=>'b',:a=>'a'})
.
Quelle est la meilleure façon de créer une chaîne de signature pour répondre à ce besoin?
Remarque: Pour le détail orienté parmi nous, je sais que vous ne pouvez pas JSON.generate()
un nombre ou une chaîne. Dans ces cas, j'appellerais simplement MD5.hexdigest()
directement.
Je coder les éléments suivants assez rapidement et je n'ai pas le temps de vraiment le tester ici au travail, mais il devrait faire le travail. Faites-moi savoir si vous rencontrez des problèmes et je vais y jeter un œil.
Cela devrait aplatir et trier correctement les tableaux et les hachages, et vous devrez avoir des chaînes d'aspect assez étrange pour qu'il y ait des collisions.
def createsig(body)
Digest::MD5.hexdigest( sigflat body )
end
def sigflat(body)
if body.class == Hash
arr = []
body.each do |key, value|
arr << "#{sigflat key}=>#{sigflat value}"
end
body = arr
end
if body.class == Array
str = ''
body.map! do |value|
sigflat value
end.sort!.each do |value|
str << value
end
end
if body.class != String
body = body.to_s << body.class.to_s
end
body
end
> sigflat({:a => {:b => 'b', :c => 'c'}, :d => 'd'}) == sigflat({:d => 'd', :a => {:c => 'c', :b => 'b'}})
=> true
Si vous ne pouviez obtenir qu'une représentation sous forme de chaîne de body
et que le hachage Ruby 1.8 revenait avec des ordres différents d'une fois à l'autre, vous pourriez hacher de manière fiable cette représentation de chaîne Saluons les mains avec quelques patchs de singe:
require 'digest/md5'
class Object
def md5key
to_s
end
end
class Array
def md5key
map(&:md5key).join
end
end
class Hash
def md5key
sort.map(&:md5key).join
end
end
Maintenant, tout objet (du type mentionné dans la question) répond à md5key
en renvoyant une clé fiable à utiliser pour créer une somme de contrôle, donc:
def createsig(o)
Digest::MD5.hexdigest(o.md5key)
end
Exemple:
body = [
{
'bar' => [
345,
"baz",
],
'qux' => 7,
},
"foo",
123,
]
p body.md5key # => "bar345bazqux7foo123"
p createsig(body) # => "3a92036374de88118faf19483fe2572e"
Remarque: Cette représentation de hachage ne code pas la structure, seulement la concaténation des valeurs. Par conséquent, ["a", "b", "c"] hachera la même chose que ["abc"].
Voici ma solution. Je parcours la structure de données et crée une liste de pièces qui sont jointes en une seule chaîne. Afin de garantir que les types de classe vus affectent le hachage, j'injecte un seul caractère unicode qui code les informations de type de base en cours de route. (Par exemple, nous voulons ["1", "2", "3"]. Objsum! = [1,2,3] .objsum)
J'ai fait cela comme un raffinement sur Object, il est facilement porté sur un patch de singe. Pour l'utiliser, il suffit de disposer du fichier et de lancer "using ObjSum".
module ObjSum
refine Object do
def objsum
parts = []
queue = [self]
while queue.size > 0
item = queue.shift
if item.kind_of?(Hash)
parts << "\\000"
item.keys.sort.each do |k|
queue << k
queue << item[k]
end
elsif item.kind_of?(Set)
parts << "\\001"
item.to_a.sort.each { |i| queue << i }
elsif item.kind_of?(Enumerable)
parts << "\\002"
item.each { |i| queue << i }
elsif item.kind_of?(Fixnum)
parts << "\\003"
parts << item.to_s
elsif item.kind_of?(Float)
parts << "\\004"
parts << item.to_s
else
parts << item.to_s
end
end
Digest::MD5.hexdigest(parts.join)
end
end
end
Juste mes 2 cents:
module Ext
module Hash
module InstanceMethods
# Return a string suitable for generating content signature.
# Signature image does not depend on order of keys.
#
# {:a => 1, :b => 2}.signature_image == {:b => 2, :a => 1}.signature_image # => true
# {{:a => 1, :b => 2} => 3}.signature_image == {{:b => 2, :a => 1} => 3}.signature_image # => true
# etc.
#
# NOTE: Signature images of identical content generated under different versions of Ruby are NOT GUARANTEED to be identical.
def signature_image
# Store normalized key-value pairs here.
ar = []
each do |k, v|
ar << [
k.is_a?(::Hash) ? k.signature_image : [k.class.to_s, k.inspect].join(":"),
v.is_a?(::Hash) ? v.signature_image : [v.class.to_s, v.inspect].join(":"),
]
end
ar.sort.inspect
end
end
end
end
class Hash #:nodoc:
include Ext::Hash::InstanceMethods
end