Создание хэша md5 числа, строки, массива или хэша в Ruby
мне нужно создать строку подписи для переменной в Ruby, где переменная может быть число, строка, хэш или массив. Хэш-значения и элементы массива также могут быть любого из этих типов.
эта строка будет использоваться для сравнения значений в базе данных (в данном случае Mongo).
моей первой мыслью было создать MD5-хэш значения, закодированного в JSON, например: (body-это переменная, упомянутая выше)
def createsig(body)
Digest::MD5.hexdigest(JSON.generate(body))
end
Это почти работает, но формат JSON.generate не кодирует ключи хэша в одном и том же порядке каждый раз, поэтому createsig({:a=>'a',:b=>'b'})
не всегда равна createsig({:b=>'b',:a=>'a'})
.
каков наилучший способ создать строку подписи в соответствии с этой необходимостью?
Примечание: Для деталей, ориентированных среди нас, я знаю, что вы не можете JSON.generate()
число или строка. В этих случаях я бы просто позвонил MD5.hexdigest()
напрямую.
5 ответов
я кодирую следующее довольно быстро и у меня нет времени, чтобы действительно проверить его здесь на работе, но он должен выполнить эту работу. Дайте мне знать, если вы найдете какие-либо проблемы с ним, и я посмотрю.
Это должно правильно сгладить и отсортировать массивы и хэши, и вам нужно будет иметь некоторые довольно странные строки, чтобы были какие-либо столкновения.
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
если бы вы могли получить только строковое представление body
и не иметь хэша Ruby 1.8 возвращаться с разными заказами от одного времени к другому, вы можете надежно хэшировать это строковое представление. Давайте запачкаем руки какими-нибудь обезьяньими заплатками:
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
теперь любой объект (из типов, упомянутых в вопросе) отвечает на md5key
возвращая надежный ключ для создания контрольной суммы, Итак:
def createsig(o)
Digest::MD5.hexdigest(o.md5key)
end
пример:
body = [
{
'bar' => [
345,
"baz",
],
'qux' => 7,
},
"foo",
123,
]
p body.md5key # => "bar345bazqux7foo123"
p createsig(body) # => "3a92036374de88118faf19483fe2572e"
Примечание: это хэш-представление не кодирует структуру, а только конкатенацию значений. Поэтому ["a"," b"," c"] будет хэшироваться так же, как ["abc"].
вот мое решение. Я хожу по структуре данных и создаю список частей, которые объединяются в одну строку. Чтобы убедиться, что типы классов влияют на хэш, я вставляю один символ unicode, который кодирует основную информацию о типе по пути. (Например, мы хотим ["1", "2", "3"].objsum != [1,2,3].objsum)
Я сделал это как уточнение объекта, он легко портирован на патч обезьяны. Чтобы использовать его, просто требуйте файл и запустите " 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
только мои 2 цента:
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