Как преобразовать объект String в объект Hash?
У меня есть строка, которая выглядит как хэш:
"{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } }"
Как мне получить Hash? как:
{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } }
Строка может иметь любую глубину вложенности. В нем есть все свойства, как в HBB напечатано допустимое значение Hash.
Ответы
Ответ 1
Строка, созданная вызовом Hash#inspect
, может быть возвращена в хеш, вызывая eval
на ней. Однако для этого необходимо, чтобы все объекты в хеше были одинаковы.
Если я начинаю с хэша {:a => Object.new}
, то его строковое представление "{:a=>#<Object:0x7f66b65cf4d0>}"
, и я не могу использовать eval
, чтобы вернуть его в хэш, потому что #<Object:0x7f66b65cf4d0>
недействителен синтаксис Ruby.
Однако, если все, что в хэше, это строки, символы, числа и массивы, оно должно работать, потому что у них есть строковые представления, которые являются истинным синтаксисом Ruby.
Ответ 2
Быстрый и грязный метод будет
eval("{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } }")
Но это имеет серьезные последствия для безопасности.
Он выполняет все, что передается, вы должны быть на 110% уверены (как, например, по крайней мере, ни один пользователь не вводит нигде в пути), он будет содержать только правильно сформированные хэши или неожиданные ошибки/ужасные существа из космоса, которые могут начать появляться.
Ответ 3
Для разных строк вы можете сделать это без использования опасного метода 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('=>', ':')
Ответ 4
Этот короткий небольшой фрагмент сделает это, но я не вижу, чтобы он работал с вложенным хэшем. Я думаю, что это довольно мило, хотя
STRING.gsub(/[{}:]/,'').split(', ').map{|h| h1,h2 = h.split('=>'); {h1 => h2}}.reduce(:merge)
Steps
1. Я уничтожаю '{', '}' и ':'
2. Я разбил строку, где бы она ни находилась,
3. Я разбил каждую из подстрок, которые были созданы с помощью раскола, всякий раз, когда он находит "= > ". Затем я создаю хэш с двумя сторонами хэша, который я просто разрознил.
4. Я остаюсь с массивом хешей, которые затем объединяю.
ПРИМЕР INPUT: "{: user_id = > 11,: blog_id = > 2,: comment_id = > 1}"
RESULT OUTPUT: { "user_id" = > "11", "blog_id" = > "2", "comment_id" = > "1" }
Ответ 5
Возможно, YAML.load?
Ответ 6
Решения до сих пор охватывают некоторые случаи, но некоторые пропускают (см. ниже). Здесь моя попытка более тщательного (безопасного) преобразования. Я знаю один угловой случай, в котором это решение не обрабатывает символы одиночного символа, состоящие из нечетных, но допустимых символов. Например, {:> => :<}
является допустимым хэш-символом 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*=>/, '\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)
Вот некоторые примечания к другим решениям здесь
Ответ 7
Я предпочитаю злоупотреблять ActiveSupport:: JSON. Их подход состоит в том, чтобы преобразовать хэш в yaml, а затем загрузить его. К сожалению, преобразование в yaml не просто, и вы, вероятно, захотите заимствовать его у AS, если у вас еще нет AS в вашем проекте.
Мы также должны преобразовать любые символы в обычные строковые ключи, поскольку символы не подходят в JSON.
Однако он не может обрабатывать хэши, у которых в них есть строка даты (наши строки даты не окружены строками, в которых возникает большая проблема):
string = '{' last_request_at ': 2011-12-28 23:00:00 UTC}'
ActiveSupport::JSON.decode(string.gsub(/:([a-zA-z])/,'\\1').gsub('=>', ' : '))
Приведёт к некорректной строковой ошибке JSON при попытке проанализировать значение даты.
Любить любые предложения о том, как справиться с этим случаем
Ответ 8
работает в rails 4.1 и поддерживает символы без кавычек {: a = > 'b'}
просто добавьте это в папку инициализаторов:
class String
def to_hash_object
JSON.parse(self.gsub(/:([a-zA-z]+)/,'"\\1"').gsub('=>', ': ')).symbolize_keys
end
end
Ответ 9
У меня была та же проблема. Я хранил хэш в Редисе. При извлечении этого хэша это была строка. Я не хотел называть eval(str)
из-за проблем с безопасностью. Мое решение состояло в том, чтобы сохранить хэш как строку json вместо строки хеша ruby. Если у вас есть опция, использование json проще.
redis.set(key, ruby_hash.to_json)
JSON.parse(redis.get(key))
TL; DR: используйте to_json
и JSON.parse
Ответ 10
Я пришел к этому вопросу после написания однострочного шрифта для этой цели, поэтому я разделяю свой код, если он помогает кому-то. Работает для строки с только одной глубиной уровня и возможными пустыми значениями (но не ноль), например:
"{ :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]}
Ответ 11
Пожалуйста, рассмотрите это решение. Библиотека + спецификация:
Файл: 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"}')
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"
require "rspec/match_result" # Get from https://github.com/dadooda/rspec_match_result.
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|
match_result(input, expected) {|input| Hash.from_string(input)}
end
end # it
end
Ответ 12
Я построил 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 безопасен.