Как преобразовать строковый объект в хэш-объект?
у меня есть строка, которая выглядит как хэш:
"{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } }"
Как мне получить хэш из него? например:
{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } }
строка может иметь любую глубину вложенности. Он имеет все свойства как допустимый хэш введенного в Ruby.
12 ответов:
строка, созданная вызовом
Hash#inspectможно превратить обратно в хэш, позвонивevalна нем. Однако это требует, чтобы то же самое было верно для всех объектов в хэше.если я начну с хэш -
{:a => Object.new}, то его строковое представление"{:a=>#<Object:0x7f66b65cf4d0>}", и я не могу использоватьevalчтобы превратить его обратно в хэш, потому что#<Object:0x7f66b65cf4d0>недопустимый синтаксис Ruby.однако, если все, что находится в хэше-это строки, символы, числа и массивы, это должно работать, потому что они имеют строковые представления, которые являются допустимым синтаксисом Ruby.
быстрый и грязный способ будет
eval("{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } }")но это имеет серьезные последствия для безопасности.
Он выполняет все, что он передается, вы должны быть уверены на 110% (как, по крайней мере, без ввода пользователя в любом месте на этом пути), он будет содержать только правильно сформированные хэши или неожиданные ошибки/ужасные существа из космоса могут начать появляться.
для разных строк, вы можете сделать это без использования dangerous
evalспособ:hash_as_string = "{\"0\"=>{\"answer\"=>\"1\", \"value\"=>\"No\"}, \"1\"=>{\"answer\"=>\"2\", \"value\"=>\"Yes\"}, \"2\"=>{\"answer\"=>\"3\", \"value\"=>\"No\"}, \"3\"=>{\"answer\"=>\"4\", \"value\"=>\"1\"}, \"4\"=>{\"value\"=>\"2\"}, \"5\"=>{\"value\"=>\"3\"}, \"6\"=>{\"value\"=>\"4\"}}" JSON.parse hash_as_string.gsub('=>', ':')
этот короткий маленький фрагмент сделает это, но я не вижу, как он работает с вложенным хэшем. Я думаю, что это довольно мило, хотя
STRING.gsub(/[{}:]/,'').split(', ').map{|h| h1,h2 = h.split('=>'); {h1 => h2}}.reduce(:merge)шагов 1. Я исключаю ' { ' ,'} 'и':' 2. Я раскалываюсь на струне везде, где она находит ',' 3. Я разделяю каждую из подстрок, которые были созданы с помощью split, всякий раз, когда он находит '=>'. Затем я создаю хэш с двумя сторонами хэша, который я только что разделил. 4. У меня остается массив хэшей, которые я затем объединяю вместе.
пример ввода: "{: user_id= > 11,: blog_id= > 2,: comment_id= > 1}" Результат вывода: {"user_id" = > "11","blog_id"=>"2","comment_id"=> "1"}
решения до сих пор охватывают некоторые случаи, но пропускают некоторые (см. ниже). Вот моя попытка более тщательного (безопасного) преобразования. Я знаю один угловой случай, который это решение не обрабатывает, который является односимвольными символами, состоящими из нечетных, но разрешенных символов. Например
{:> => :<}является допустимым хэшем ruby.Я поставил этот код на github, а также. Этот код начинается с тестовой строки для выполнения всех преобразований
require 'json' # Example ruby hash string which exercises all of the permutations of position and type # See http://json.org/ ruby_hash_text='{"alpha"=>{"first second > third"=>"first second > third", "after comma > foo"=>:symbolvalue, "another after comma > foo"=>10}, "bravo"=>{:symbol=>:symbolvalue, :aftercomma=>10, :anotheraftercomma=>"first second > third"}, "charlie"=>{1=>10, 2=>"first second > third", 3=>:symbolvalue}, "delta"=>["first second > third", "after comma > foo"], "echo"=>[:symbol, :aftercomma], "foxtrot"=>[1, 2]}' puts ruby_hash_text # Transform object string symbols to quoted strings ruby_hash_text.gsub!(/([{,]\s*):([^>\s]+)\s*=>/, '""=>') # Transform object string numbers to quoted strings ruby_hash_text.gsub!(/([{,]\s*)([0-9]+\.?[0-9]*)\s*=>/, '""=>') # Transform object value symbols to quotes strings ruby_hash_text.gsub!(/([{,]\s*)(".+?"|[0-9]+\.?[0-9]*)\s*=>\s*:([^,}\s]+\s*)/, '=>""') # Transform array value symbols to quotes strings ruby_hash_text.gsub!(/([\[,]\s*):([^,\]\s]+)/, '""') # Transform object string object value delimiter to colon delimiter ruby_hash_text.gsub!(/([{,]\s*)(".+?"|[0-9]+\.?[0-9]*)\s*=>/, ':') puts ruby_hash_text puts JSON.parse(ruby_hash_text)вот некоторые заметки о другие решения здесь
- @Ken Bloom и @Toms Mikoss ' s решения использовать
evalчто слишком страшно для меня (как справедливо указывает Томс).- @zolter ' s решение работает, если ваш хэш не имеет никаких символов или цифровых клавиш.
- @jackquack ' s решение работает, если нет строк в кавычках, смешанный с атрибутика.
- @Eugene ' s решение работает, если ваши символы не используют все разрешенные символы (символьные литералы имеют более широкий набор разрешенных символов).
- @Pablo ' s решение работает до тех пор, пока у вас нет сочетания символов и цитируемых строк.
Я предпочитаю злоупотреблять ActiveSupport:: JSON. Их подход заключается в преобразовании хэша в yaml, а затем загрузить его. К сожалению, преобразование в yaml не просто, и вы, вероятно, захотите заимствовать его, как будто у вас уже нет, как в вашем проекте.
мы также должны преобразовать любые символы в обычные строковые ключи, поскольку символы не подходят в JSON.
однако он не может обрабатывать хэши, в которых есть строка даты (наши строки даты в конечном итоге не являются в окружении строк, где и возникает большая проблема):
string = ' {'last_request_at': 2011-12-28 23:00: 00 UTC }'
ActiveSupport::JSON.decode(string.gsub(/:([a-zA-z])/,'\1').gsub('=>', ' : '))приведет к недопустимой ошибке строки JSON при попытке проанализировать значение даты.
хотелось бы получить какие-либо предложения о том, как справиться с этим делом
у меня была та же проблема. Я хранил хэш в редисе. При получении этого хэша, это была строка. Я не хотел звонить
eval(str)из соображений безопасности. Мое решение состояло в том, чтобы сохранить хэш как строку json вместо строки хэша ruby. Если у вас есть возможность, использование json проще.redis.set(key, ruby_hash.to_json) JSON.parse(redis.get(key))TL; DR: use
to_jsonиJSON.parse
работает в rails 4.1 и поддерживает символы без кавычек {: a = > 'b'}
просто добавьте это в папку инициализаторов:
class String def to_hash_object JSON.parse(self.gsub(/:([a-zA-z]+)/,'"\1"').gsub('=>', ': ')).symbolize_keys end end
Я пришел к этому вопросу после написания однострочного для этой цели, поэтому я делюсь своим кодом, если это кому-то поможет. Работает для строки только с одной глубиной уровня и возможными пустыми значениями( но не nil), например:
"{ :key_a => 'value_a', :key_b => 'value_b', :key_c => '' }"код:
the_string = '...' the_hash = Hash.new the_string[1..-2].split(/, /).each {|entry| entryMap=entry.split(/=>/); value_str = entryMap[1]; the_hash[entryMap[0].strip[1..-1].to_sym] = value_str.nil? ? "" : value_str.strip[1..-2]}
Я построил драгоценный камень hash_parser это сначала проверяет, является ли хэш безопасным или не использует
ruby_parserкамень. Только тогда он применяетeval.вы можете использовать его как
require 'hash_parser' # this executes successfully a = "{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } }" p HashParser.new.safe_load(a) # this throws a HashParser::BadHash exception a = "{ :key_a => system('ls') }" p HashParser.new.safe_load(a)тесты в https://github.com/bibstha/ruby_hash_parser/blob/master/test/test_hash_parser.rb приведу вам больше примеров того, что я проверил, чтобы убедиться, что eval безопасен.
пожалуйста, рассмотрите это решение. Библиотека+спецификации:
File:
lib/ext/hash/from_string.rb:require "json" module Ext module Hash module ClassMethods # Build a new object from string representation. # # from_string('{"name"=>"Joe"}') # # @param s [String] # @return [Hash] def from_string(s) s.gsub!(/(?<!\)"=>nil/, '":null') s.gsub!(/(?<!\)"=>/, '":') JSON.parse(s) end end end end class Hash #:nodoc: extend Ext::Hash::ClassMethods endFile:
spec/lib/ext/hash/from_string_spec.rb:require "ext/hash/from_string" describe "Hash.from_string" do it "generally works" do [ # Basic cases. ['{"x"=>"y"}', {"x" => "y"}], ['{"is"=>true}', {"is" => true}], ['{"is"=>false}', {"is" => false}], ['{"is"=>nil}', {"is" => nil}], ['{"a"=>{"b"=>"c","ar":[1,2]}}', {"a" => {"b" => "c", "ar" => [1, 2]}}], ['{"id"=>34030, "users"=>[14105]}', {"id" => 34030, "users" => [14105]}], # Tricky cases. ['{"data"=>"{\"x\"=>\"y\"}"}', {"data" => "{\"x\"=>\"y\"}"}], # Value is a `Hash#inspect` string which must be preserved. ].each do |input, expected| output = Hash.from_string(input) expect([input, output]).to eq [input, expected] end end # it end
Comments