Создание хеша 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()
напрямую.
Ответы
Ответ 1
Я быстро кодирую следующее и не имею времени, чтобы действительно проверить его здесь на работе, но он должен выполнять эту работу. Дайте мне знать, если вы найдете какие-либо проблемы с этим, и я посмотрю.
Это должно правильно сгладить и отсортировать массивы и хэши, и вам нужно будет иметь некоторые довольно странные строки, чтобы там были какие-либо столкновения.
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
Ответ 2
Если бы вы могли получить только строковое представление 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" ].
Ответ 3
Вот мое решение. Я прохожу структуру данных и создаю список частей, которые объединяются в одну строку. Чтобы убедиться, что типы классов видны, влияют на хеш, я вставляю один символ Юникода, который кодирует информацию базового типа на этом пути. (Например, мы хотим [ "1", "2", "3" ]. Objsum!= [1,2,3].objsum)
Я сделал это как уточнение объекта, он легко портирован на патч обезьяны. Для его использования требуется только файл и запустить "с помощью 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
Ответ 4
Только мои 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
Ответ 5
В зависимости от ваших потребностей вы можете даже звонить ary.inspect
или ary.to_yaml
.