programing

String 개체를 Hash 개체로 변환하려면 어떻게 해야 합니까?

oldcodes 2023. 6. 3. 08:42
반응형

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)

다음은 다른 솔루션에 대한 참고 사항입니다.

이 짧은 스니펫으로 할 수 있지만, 중첩된 해시로 작동하는 것을 볼 수 없습니다.그래도 꽤 귀여운 것 같아요.

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방법들.

데이터에 대해 다음과 같은 가정을 합니다.

  1. 해시 키는 문자열, 기호 또는 정수로 가정됩니다.
  2. 해시 값은 문자열, 기호, 정수, 부울, 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

반응형