String 개체를 Hash 개체로 변환하려면 어떻게 해야 합니까?
해시처럼 보이는 문자열이 있습니다.
"{ :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에서 유효한 해시를 입력하는 방법에 대한 모든 속성이 있습니다.
다른 문자열의 경우 위험하지 않고 수행할 수 있습니다.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('=>', ':')
빠르고 더러운 방법은
eval("{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } }")
하지만 그것은 심각한 보안상의 영향을 미칩니다.
통과된 것이 무엇이든 실행합니다. 적어도 도중에 사용자 입력이 없는 경우에는 제대로 형성된 해시만 포함되어 있거나 예상치 못한 외계의 버그/끔찍한 생물이 나타날 수 있다는 것을 110% 확신해야 합니다.
을 호출하여 Hash#inspect
를 호출하여 해시로 다시 변환할 수 있습니다.eval
그러나 해시의 모든 개체에 대해 동일해야 합니다.
▁▁if로 하면,{:a => Object.new}
은 그다면그문자표현은열의것렇표▁then입니다."{:a=>#<Object:0x7f66b65cf4d0>}"
그리고 나는 사용할 수 없습니다.eval
그것을 다시 해시로 바꾸는 것은 왜냐하면.#<Object:0x7f66b65cf4d0>
올바른 Ruby 구문이 아닙니다.
그러나 해시에 있는 것이 문자열, 기호, 숫자 및 배열뿐이라면 유효한 Ruby 구문의 문자열 표현을 가지고 있기 때문에 작동해야 합니다.
저도 같은 문제가 있었습니다.레디스에 해시를 저장하고 있었어요해당 해시를 검색할 때는 문자열이었습니다.전화하고 싶지 않았습니다.eval(str)
보안상의 문제 때문에.제 해결책은 해시를 루비 해시 문자열이 아닌 json 문자열로 저장하는 것이었습니다.옵션이 있다면 json을 사용하는 것이 더 쉽습니다.
redis.set(key, ruby_hash.to_json)
JSON.parse(redis.get(key))
TL 사용;DR: 사용to_json
그리고.JSON.parse
아마 YAML.load?
지금까지의 솔루션은 일부 사례를 다루지만 일부 사례는 누락되었습니다(아래 참조).좀 더 철저한 (안전한) 전환을 시도해 보겠습니다.저는 이 솔루션이 처리하지 못하는 홀수 문자로 구성된 단일 문자 기호를 알고 있지만 허용되는 한 코너 케이스를 알고 있습니다.를 들어 를들면입니다.{:> => :<}
유효한 루비 해시입니다.
이 코드도 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)
다음은 다른 솔루션에 대한 참고 사항입니다.
- @Ken Bloom 및 @Toms Mikoss의 솔루션 사용
eval
(톰스가 올바르게 지적했듯이) 그것은 나에게 너무 무서운 것입니다. - @zolter의 솔루션은 해시에 기호나 숫자 키가 없는 경우에 작동합니다.
- @jackquack의 솔루션은 따옴표로 묶인 문자열이 없는 경우 작동합니다.
- @Eugene의 솔루션은 기호가 허용된 모든 문자를 사용하지 않는 경우에 작동합니다(기호 리터럴은 허용된 문자 집합이 더 넓음).
- @Pablo의 솔루션은 기호와 따옴표로 묶인 문자열이 없는 한 작동합니다.
이 짧은 스니펫으로 할 수 있지만, 중첩된 해시로 작동하는 것을 볼 수 없습니다.그래도 꽤 귀여운 것 같아요.
STRING.gsub(/[{}:]/,'').split(', ').map{|h| h1,h2 = h.split('=>'); {h1 => h2}}.reduce(:merge)
1단계.저는 '{',','}과 ':' 2를 제거합니다.저는 문자열이 ','를 찾을 때마다 분할합니다. 3. 분할과 함께 생성된 각 하위 문자열이 '=>'를 찾을 때마다 분할합니다.그다음에 제가 방금 나눈 해시의 양면으로 해시를 만듭니다. 4.저에게는 해시 배열이 남아 있고, 그 해시 배열은 함께 병합됩니다.
입력 예: "{:user_id=>11, :syslog_id=>2, :syslog_id=>1}" 결과 출력: {"user_id"=>"11", "syslog_id"=>"2", "syslog_id"=>"1"}
ActiveSupport를 악용하는 것이 더 좋습니다.:JSON. 그들의 접근 방식은 해시를 yaml로 변환한 다음 로드하는 것입니다.안타깝게도 yaml로의 변환은 간단하지 않으며 프로젝트에 아직 AS가 없다면 AS에서 빌리고 싶을 것입니다.
또한 JSON에서는 기호가 적절하지 않기 때문에 기호를 일반 문자열 키로 변환해야 합니다.
그러나 날짜 문자열이 포함된 해시를 처리할 수 없습니다(우리의 날짜 문자열은 문자열로 둘러싸이지 않게 되어 큰 문제가 발생합니다).
= 23:}' = '{'last_request_at' : 2011-12-28 23:00:00 UTC }'ActiveSupport::JSON.decode(string.gsub(/:([a-zA-z])/,'\\1').gsub('=>', ' : '))
날짜 값을 구문 분석할 때 잘못된 JSON 문자열 오류가 발생합니다.
이 사건을 처리하는 방법에 대한 제안이 있으면 좋겠습니다.
레일 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
여기 두 가지보다 안전한 화이트쿼크/파서를 사용하는 방법이 있습니다.gsub
그리고.eval
방법들.
데이터에 대해 다음과 같은 가정을 합니다.
- 해시 키는 문자열, 기호 또는 정수로 가정됩니다.
- 해시 값은 문자열, 기호, 정수, 부울, nil, 배열 또는 해시로 가정됩니다.
# frozen_string_literal: true
require 'parser/current'
class HashParser
# Type error is used to handle unexpected types when parsing stringified hashes.
class TypeError < ::StandardError
attr_reader :message, :type
def initialize(message, type)
@message = message
@type = type
end
end
def hash_from_s(str_hash)
ast = Parser::CurrentRuby.parse(str_hash)
unless ast.type == :hash
puts "expected data to be a hash but got #{ast.type}"
return
end
parse_hash(ast)
rescue Parser::SyntaxError => e
puts "error parsing hash: #{e.message}"
rescue TypeError => e
puts "unexpected type (#{e.type}) encountered while parsing: #{e.message}"
end
private
def parse_hash(hash)
out = {}
hash.children.each do |node|
unless node.type == :pair
raise TypeError.new("expected child of hash to be a `pair`", node.type)
end
key, value = node.children
key = parse_key(key)
value = parse_value(value)
out[key] = value
end
out
end
def parse_key(key)
case key.type
when :sym, :str, :int
key.children.first
else
raise TypeError.new("expected key to be either symbol, string, or integer", key.type)
end
end
def parse_value(value)
case value.type
when :sym, :str, :int
value.children.first
when :true
true
when :false
false
when :nil
nil
when :array
value.children.map { |c| parse_value(c) }
when :hash
parse_hash(value)
else
raise TypeError.new("value of a pair was an unexpected type", value.type)
end
end
end
다음은 예상대로 작동하는지 확인하는 몇 가지 rspec 테스트입니다.
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe HashParser do
describe '#hash_from_s' do
subject { described_class.new.hash_from_s(input) }
context 'when input contains forbidden types' do
where(:input) do
[
'def foo; "bar"; end',
'`cat somefile`',
'exec("cat /etc/passwd")',
'{:key=>Env.fetch("SOME_VAR")}',
'{:key=>{:another_key=>Env.fetch("SOME_VAR")}}',
'{"key"=>"value: #{send}"}'
]
end
with_them do
it 'returns nil' do
expect(subject).to be_nil
end
end
end
context 'when input cannot be parsed' do
let(:input) { "{" }
it 'returns nil' do
expect(subject).to be_nil
end
end
context 'with valid input' do
using RSpec::Parameterized::TableSyntax
where(:input, :expected) do
'{}' | {}
'{"bool"=>true}' | { 'bool' => true }
'{"bool"=>false}' | { 'bool' => false }
'{"nil"=>nil}' | { 'nil' => nil }
'{"array"=>[1, "foo", nil]}' | { 'array' => [1, "foo", nil] }
'{foo: :bar}' | { foo: :bar }
'{foo: {bar: "bin"}}' | { foo: { bar: "bin" } }
end
with_them do
specify { expect(subject).to eq(expected) }
end
end
end
end
먼저 해시가 안전한지 여부를 확인하는 gem 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 의 테스트는 평가가 안전한지 확인하기 위해 테스트한 것들의 더 많은 예를 제공합니다.
이 메서드는 한 수준의 딥 해시에 대해 작동합니다.
def convert_to_hash(str)
return unless str.is_a?(String)
hash_arg = str.gsub(/[^'"\w\d]/, ' ').squish.split.map { |x| x.gsub(/['"]/, '') }
Hash[*hash_arg]
end
예
> convert_to_hash("{ :key_a => 'value_a', :key_b => 'value_b', :key_c => '' }")
=> {"key_a"=>"value_a", "key_b"=>"value_b", "key_c"=>""}
저는 이 목적으로 한 줄을 작성한 후에 이 질문을 하게 되었습니다. 그래서 누군가에게 도움이 될 경우를 대비해 코드를 공유합니다.다음과 같이 하나의 레벨 깊이와 가능한 빈 값(0은 아님)을 가진 문자열에 대해 작동합니다.
"{ :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에서 데이터를 가져와서 로컬로 파일에 쓰는 중이었습니다.그러면 파일에서 데이터를 가져와서 해시를 사용할 수 있습니다.
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.read를 대신 사용할 수도 있습니다.
루비에서 문자열을 해시로 변환할 때도 비슷한 문제가 있었습니다.
제가 계산한 결과는 다음과 같습니다.
{
"coord":{"lon":24.7535,"lat":59.437},
"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],
"base":"stations",
"main":{"temp":283.34,"feels_like":281.8,"temp_min":282.33,"temp_max":283.34,"pressure":1021,"humidity":53},
"visibility":10000,
"wind":{"speed":3.09,"deg":310},
"clouds":{"all":75},
"dt":1652808506,
"sys":{"type":1,"id":1330,"country":"EE","sunrise":1652751796,"sunset":1652813502},
"timezone":10800,"id":588409,"name":"Tallinn","cod":200
}
아래 명령을 사용하여 유형 값을 확인하고 문자열 유형임을 확인했습니다.
result =
{
"coord":{"lon":24.7535,"lat":59.437},
"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],
"base":"stations",
"main":{"temp":283.34,"feels_like":281.8,"temp_min":282.33,"temp_max":283.34,"pressure":1021,"humidity":53},
"visibility":10000,
"wind":{"speed":3.09,"deg":310},
"clouds":{"all":75},
"dt":1652808506,
"sys":{"type":1,"id":1330,"country":"EE","sunrise":1652751796,"sunset":1652813502},
"timezone":10800,"id":588409,"name":"Tallinn","cod":200
}
puts result.instance_of? String
puts result.instance_of? Hash
해결 방법은 다음과 같습니다.
아래 명령을 실행하여 문자열에서 해시로 변환하기만 하면 되었습니다.
result_new = JSON.parse(result, symbolize_names: true)
그런 다음 아래 명령을 사용하여 유형 값을 다시 확인합니다.
puts result_new.instance_of? String
puts result_new.instance_of? Hash
이번에 그것이 돌아왔습니다.true
해쉬를 위하여
언급URL : https://stackoverflow.com/questions/1667630/how-do-i-convert-a-string-object-into-a-hash-object
'programing' 카테고리의 다른 글
안드로이드에서 이미지 보기에 대한 틴트를 프로그래밍 방식으로 설정하는 방법은 무엇입니까? (0) | 2023.06.03 |
---|---|
배경 이미지와 CSS3 그라데이션을 동일한 요소에 결합하려면 어떻게 해야 합니까? (0) | 2023.06.03 |
폴더 선택 대화상자 WPF (0) | 2023.06.03 |
현재 날짜/시간을 DD/MM/YYYY HH:MM 형식으로 가져오려면 어떻게 해야 합니까? (0) | 2023.06.03 |
스토리보드에서 탭 모음 컨트롤러 순서 재정렬 (0) | 2023.06.03 |