String 객체를 Hash 객체로 어떻게 변환합니까?


136

해시처럼 보이는 문자열이 있습니다.

"{ :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이 여기서 무언가를 할 것이라고 생각합니다. 먼저 테스트하겠습니다. 생각보다 일찍 질문을 게시했습니다. :)
Waseem

오 예, 그냥 평가판에 전달하십시오. :)
Waseem

답변:


79

호출하여 생성 된 문자열은 호출 Hash#inspect하여 해시로 다시 전환 될 수 있습니다.eval . 그러나 이것은 해시의 모든 객체에 대해 동일해야합니다.

hash로 시작하면 {:a => Object.new}문자열 표현은 "{:a=>#<Object:0x7f66b65cf4d0>}"이며 유효한 Ruby 구문이 아니기 eval때문에 문자열을 해시로 되돌릴 수 없습니다 #<Object:0x7f66b65cf4d0>.

그러나 해시에있는 모든 것이 문자열, 기호, 숫자 및 배열이면 유효한 Ruby 구문 인 문자열 표현이 있으므로 작동해야합니다.


"해시에있는 모든 것이 문자열, 기호 및 숫자이면" 이것은 많은 것을 말합니다. 따라서 eval위의 명령문이 해당 문자열에 유효한지 확인하여 해시 로 사용할 문자열의 유효성을 확인할 수 있습니다 .
Waseem

1
그렇습니다. 그러나이를 위해서는 완전한 루비 파서가 필요하거나 문자열이 어디에서 왔는지 알고 문자열, 기호 및 숫자 만 생성 할 수 있다는 것을 알아야합니다. (문자열의 내용을 신뢰하는 것에 대한 Toms Mikoss의 답변도 참조하십시오.)
Ken Bloom

13
이것을 사용하는 곳에 조심하십시오. eval잘못된 장소에서 사용 하는 것은 큰 보안 허점입니다. 문자열 내부의 모든 것이 평가됩니다. API에 누군가가 주입 rm -fr
했다고

153

다른 문자열의 경우 위험한 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('=>', ':')

2
eval을 사용하지 않으려면이 답변을 선택해야합니다.
Michael_Zhang

4
당신은 또한, 철 NILS를 교체해야합니다JSON.parse(hash_as_string.gsub("=>", ":").gsub(":nil,", ":null,"))
요 Ludke

136

빠르고 더러운 방법은

eval("{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } }") 

그러나 보안에 심각한 영향을 미칩니다.
전달 된 모든 것을 실행하면 110 % 확신해야합니다 (적어도 어느 곳에도 사용자 입력이 없어야 함) 제대로 구성된 해시 만 포함하거나 외부 공간에서 예기치 않은 버그 / 끔찍한 생물이 팝업되기 시작할 수 있습니다.


16
나는 나와 함께 가벼운 세이버가 있습니다. 나는 그 생물과 벌레를 돌볼 수 있습니다. :)
Waseem

12
선생님에 따르면 EVAL 사용은 여기에서 위험 할 수 있습니다. Eval은 루비 코드를 가져 와서 실행합니다. 여기에서의 위험은 SQL 주입 위험과 유사합니다. Gsub가 바람직합니다.
boulder_ruby

9
David의 교사가 올바른 이유를 보여주는 문자열 예 : '{: surprise => "# {system \"rm -rf * \ "}"}'
A. Wilson

13
여기서 EVAL 사용의 위험을 충분히 강조 할 수 없습니다! 사용자 입력이 문자열에 감길 수 있다면 이것은 절대적으로 금지되어 있습니다.
Dave Collins

더 공개적으로 열지 않을 것이라고 생각하더라도 다른 사람이 할 수 있습니다. 우리 모두는 예상치 못한 방식으로 코드가 어떻게 사용되는지 알고 있어야합니다. 마치 높은 선반에 굉장히 무거운 물건을 올려 놓는 것과 같습니다. 이런 형태의 위험을 만들면 안됩니다.
스티브 세터

24

아마 YAML.load?


(로드 방법은 문자열을 지원합니다)
자동

5
완전히 다른 문자열 표현이 필요하지만 훨씬 안전합니다. (그리고 문자열 표현은 생성하기 쉽습니다. #inspect 대신 #to_yaml을 호출하면됩니다)
Ken Bloom

와. yaml이있는 문자열을 구문 분석하는 것이 너무 쉽다는 것을 몰랐습니다. 데이터를 생성하고 문자열 형식의 마사지없이 루비 해시로 지능적으로 전환시키는 Linux bash 명령 체인이 필요합니다.
미로

this 및 to_yaml은 문자열 생성 방식을 제어 할 수 있기 때문에 내 문제를 해결합니다. 감사!
mlabarca

23

이 짧은 스 니펫은 그렇게하지만 중첩 된 해시와 함께 작동하는 것을 볼 수 없습니다. 그래도 꽤 귀엽다고 생각합니다

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"}


1
그것은 하나의 아픈 oneliner입니다! :) +1
blushrt 13:23에

3
이것은 또한 {}:문자열 화 된 해시 내부의 에서 문자를 제거하지 않습니까?
Vladimir Panteleev

@VladimirPanteleev 당신 말이 맞아요. 좋은 캐치! 당신은 내 코드 리뷰를 언제든지 할 수 있습니다 :)
hrdwdmrbl

20

지금까지 솔루션은 일부 사례를 다루었지만 일부 사례는 누락되었습니다 (아래 참조). 보다 철저한 (안전한) 변환을위한 나의 시도는 다음과 같습니다. 이 솔루션이 홀수이지만 허용되는 문자로 구성된 단일 문자 기호를 처리하지 못하는 경우를 알고 있습니다. 예를 들어{:> => :<} 유효한 루비 해시입니다.

코드를 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)

여기 다른 솔루션에 대한 참고 사항이 있습니다.


매우 멋진 솔루션. 특정 이상한 점을 처리하기 위해 모든 부분 :nil을 추가 할 수 있습니다 :null.
SteveTurczyn

1
이 솔루션은 JSON # parse를 활용하기 때문에 다단계 해시를 재귀 적으로 작업 할 수 있습니다. 다른 솔루션에 중첩하는 데 문제가있었습니다.
Patrick 읽기

17

나는 같은 문제가 있었다. Redis에 해시를 저장하고있었습니다. 해시를 검색 할 때 문자열이었습니다. eval(str)보안 문제 때문에 전화를 걸고 싶지 않았습니다 . 내 솔루션은 해시를 루비 해시 문자열 대신 json 문자열로 저장하는 것이 었습니다. 옵션이 있으면 json을 사용하는 것이 더 쉽습니다.

  redis.set(key, ruby_hash.to_json)
  JSON.parse(redis.get(key))

TL; DR : 사용 to_jsonJSON.parse


1
이것이 가장 좋은 답변입니다. to_jsonJSON.parse
ardochhigh

3
누구든지 나를 억압했다. 왜? 루비 해시의 문자열 표현을 실제 해시 객체로 변환하려고 시도하면서 동일한 문제가 발생했습니다. 나는 잘못된 문제를 해결하려고한다는 것을 깨달았습니다. 여기에서 묻는 질문을 해결하는 것은 오류가 발생하기 쉽고 안전하지 않다는 것을 깨달았습니다. 데이터를 다르게 저장하고 객체를 안전하게 직렬화하고 역 직렬화하도록 설계된 형식을 사용해야한다는 것을 깨달았습니다. TL; DR : OP와 같은 질문이 있었고, 그 대답은 다른 질문을하는 것임을 깨달았습니다. 또한 귀하가 저에게 투표를한다면 우리 모두 함께 배울 수 있도록 피드백을 제공하십시오.
Jared Menard

3
설명이없는 하향 투표는 스택 오버플로의 암입니다.
ardochhigh

1
예, downvoting은 설명이 필요하고 누가 downvotes를 보여 주어야합니다.
닉 입술

2
이 답변을 OP의 질문에 더 적용하기 위해 해시의 문자열 표현이 'strungout'인 경우 hashit = JSON.parse (strungout.to_json)을 수행 한 다음 hashit을 통해 hashit [ 일반적으로 'keyname'].
cixelsyd

11

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 문자열 오류가 발생합니다.

이 사건을 처리하는 방법에 대한 제안을 듣고 싶습니다


2
.decode에 대한 포인터에 감사드립니다. 테스트하기 위해 JSON 응답을 변환해야했습니다. 내가 사용한 코드는 다음과 같습니다.ActiveSupport::JSON.decode(response.body, symbolize_keys: true)
Andrew Philips

9

레일 4.1에서 작동하며 따옴표없이 기호를 지원합니다 {: a => 'b'}

초기화 폴더에 이것을 추가하십시오 :

class String
  def to_hash_object
    JSON.parse(self.gsub(/:([a-zA-z]+)/,'"\\1"').gsub('=>', ': ')).symbolize_keys
  end
end

명령 줄에서 작동하지만, 내가 ... 내가 intializer에 넣고 때 "깊은에 스택 레벨"얻을
알렉스 에델

2

먼저 해시가 안전한지 또는 gem을 사용하지 않는지 확인 하는 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 의 테스트는 eval이 안전한지 테스트 한 것의 더 많은 예를 제공합니다.


2

이 솔루션을 고려하십시오. 라이브러리 + 사양 :

파일 : 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

1
it "generally works" 그러나 반드시 그런 것은 아닙니다? 나는 그 테스트에서 더 장황 할 것입니다. it "converts strings to object" { expect('...').to eql ... } it "supports nested objects" { expect('...').to eql ... }
Lex

안녕하세요 @Lex, 그 방법은 RubyDoc 주석에 설명되어 있습니다. 테스트는 상태를 다시 밝히지 않는 것이 좋으며 불필요한 텍스트를 수동 텍스트로 만듭니다. 따라서 "일반적으로 효과가있다"는 것은 일반적으로 효과가 있다는 것을 나타내는 훌륭한 공식입니다. 건배!
Alex Fortuna

예, 하루가 끝날 무렵에는 무엇이든지 작동합니다. 모든 테스트는 테스트가없는 것보다 낫습니다. 개인적으로 나는 명백한 설명의 팬이지만, 그것은 단지 선호입니다.
Lex

1

이 목적을 위해 하나의 라이너를 작성한 후이 질문에 왔으므로 누군가를 도울 수 있도록 코드를 공유합니다. 다음과 같이 단 하나의 레벨 깊이와 가능한 빈 값 (무 (NULL)은 아님)이있는 문자열에서 작동합니다.

"{ :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]}

0

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의 조상이라고 언급합니다. 원하는 경우 File.read를 대신 사용할 수도 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.