У меня есть строка, похожая на хеш:
"{ :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 вводится действительный хэш.
Ответы:
Строку, созданную при вызове,
Hash#inspect
можно снова превратить в хэш, вызвавeval
ее. Однако для этого требуется, чтобы то же самое было верно для всех объектов в хэше.Если я начну с хеша
{:a => Object.new}
, то это будет строковое представление"{:a=>#<Object:0x7f66b65cf4d0>}"
, и я не могу использовать его,eval
чтобы снова превратить его в хеш, потому что#<Object:0x7f66b65cf4d0>
это недопустимый синтаксис Ruby.Однако, если все, что находится в хэше, - это строки, символы, числа и массивы, он должен работать, потому что они имеют строковые представления, соответствующие синтаксису Ruby.
источник
eval
как хэш, убедившись, что приведенное выше утверждение действительно для этой строки.eval
в неправильном месте - огромная дыра в безопасности. Все, что находится внутри строки, будет оцениваться. Так что представьте, если бы в API кто-то ввелrm -fr
Для другой строки это можно сделать без использования опасного
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('=>', ':')
источник
JSON.parse(hash_as_string.gsub("=>", ":").gsub(":nil,", ":null,"))
Быстрый и грязный метод был бы
eval("{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } }")
Но это имеет серьезные последствия для безопасности.
Он выполняет все, что было передано, вы должны быть на 110% уверены (например, по крайней мере, отсутствие пользовательского ввода где-либо на пути), что он будет содержать только правильно сформированные хеши или неожиданные ошибки / ужасные существа из космоса могут начать появляться.
источник
Может быть, YAML.load?
источник
Этот небольшой фрагмент сделает это, но я не вижу, чтобы он работал с вложенным хешем. Я думаю это довольно мило
STRING.gsub(/[{}:]/,'').split(', ').map{|h| h1,h2 = h.split('=>'); {h1 => h2}}.reduce(:merge)
Шаги 1. Я удаляю '{', '}' и ':' 2. Я разделяю строку везде, где она находит ',' 3. Я разделяю каждую из подстрок, которые были созданы с помощью разделения, всякий раз, когда она находит а '=>'. Затем я создаю хеш с двумя сторонами хеша, которые я только что разделил. 4. У меня остается массив хешей, которые я затем объединяю.
ПРИМЕР ВВОДА: "{: user_id => 11,: blog_id => 2,: comment_id => 1}" ВЫВОД РЕЗУЛЬТАТА: {"user_id" => "11", "blog_id" => "2", "comment_id" = > "1"}
источник
{}:
символы из значений внутри строкового хеша?Решения пока охватывают некоторые случаи, но упускают некоторые из них (см. Ниже). Вот моя попытка более тщательного (безопасного) преобразования. Я знаю один угловой случай, который это решение не обрабатывает, - это односимвольные символы, состоящие из нечетных, но разрешенных символов. Например,
{:> => :<}
действительный рубиновый хеш.Я также разместил этот код на 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*=>/, '\1"\2"=>') # Transform object string numbers to quoted strings ruby_hash_text.gsub!(/([{,]\s*)([0-9]+\.?[0-9]*)\s*=>/, '\1"\2"=>') # Transform object value symbols to quotes strings ruby_hash_text.gsub!(/([{,]\s*)(".+?"|[0-9]+\.?[0-9]*)\s*=>\s*:([^,}\s]+\s*)/, '\1\2=>"\3"') # Transform array value symbols to quotes strings ruby_hash_text.gsub!(/([\[,]\s*):([^,\]\s]+)/, '\1"\2"') # Transform object string object value delimiter to colon delimiter ruby_hash_text.gsub!(/([{,]\s*)(".+?"|[0-9]+\.?[0-9]*)\s*=>/, '\1\2:') puts ruby_hash_text puts JSON.parse(ruby_hash_text)
Вот несколько заметок о других решениях здесь
eval
который слишком страшно для меня (как Toms справедливо указывает).источник
:nil
к:null
к ручке конкретной странности.У меня такая же проблема. Я хранил хеш в Redis. При получении этого хеша это была строка. Я не хотел звонить
eval(str)
из соображений безопасности. Мое решение заключалось в том, чтобы сохранить хэш как строку json вместо строки хеша рубина. Если у вас есть возможность, использовать json проще.TL; DR: использовать
to_json
иJSON.parse
источник
to_json
andJSON.parse
Я предпочитаю злоупотреблять ActiveSupport :: JSON. Их подход состоит в том, чтобы преобразовать хеш в yaml, а затем загрузить его. К сожалению, преобразование в yaml непросто, и вы, вероятно, захотите позаимствовать его у AS, если у вас еще нет AS в вашем проекте.
Мы также должны преобразовать любые символы в обычные строковые ключи, поскольку символы не подходят в JSON.
Однако он не может обрабатывать хэши, в которых есть строка даты (наши строки даты в конечном итоге не окружены строками, и здесь возникает большая проблема):
строка = '{' last_request_at ': 2011-12-28 23:00:00 UTC}'
ActiveSupport::JSON.decode(string.gsub(/:([a-zA-z])/,'\\1').gsub('=>', ' : '))
При попытке проанализировать значение даты приведет к ошибке неверной строки JSON.
Хотел бы получить предложения о том, как справиться с этим делом
источник
ActiveSupport::JSON.decode(response.body, symbolize_keys: true)
работает в рельсах 4.1 и поддерживает символы без кавычек {: a => 'b'}
просто добавьте это в папку инициализаторов:
class String def to_hash_object JSON.parse(self.gsub(/:([a-zA-z]+)/,'"\\1"').gsub('=>', ': ')).symbolize_keys end end
источник
Пожалуйста, рассмотрите это решение. Библиотека + спецификация:
Файл
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 end
Файл
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
источник
it "generally works"
но не обязательно? Я был бы более подробным в этих тестах.it "converts strings to object" { expect('...').to eql ... }
it "supports nested objects" { expect('...').to eql ... }
Я создал gem hash_parser, который сначала проверяет, является ли хеш безопасным или не использует
ruby_parser
gem. Только после этого применяется расширение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 безопасен.
источник
Я пришел к этому вопросу после того, как написал для этой цели однострочник, поэтому я делюсь своим кодом на случай, если он кому-то поможет. Работает для строки только с одной глубиной уровня и возможными пустыми значениями (но не с 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]}
источник
Возникла аналогичная проблема, которая требовала использования eval ().
В моей ситуации я извлекал некоторые данные из API и записывал их в файл локально. Затем возможность извлечь данные из файла и использовать Hash.
Я использовал IO.read () для чтения содержимого файла в переменную. В этом случае IO.read () создает его как строку.
Затем использовал eval () для преобразования строки в хэш.
read_handler = IO.read("Path/To/File.json") puts read_handler.kind_of?(String) # Returns TRUE a = eval(read_handler) puts a.kind_of?(Hash) # Returns TRUE puts a["Enter Hash Here"] # Returns Key => Values puts a["Enter Hash Here"].length # Returns number of key value pairs puts a["Enter Hash Here"]["Enter Key Here"] # Returns associated value
Также просто упомяну, что IO является предком File. Так что вы также можете использовать File.read, если хотите.
источник