cách tốt nhất để chuyển đổi cặp giá trị khóa được định dạng json thành hàm băm ruby ​​với biểu tượng là khóa là gì?


103

Tôi đang tự hỏi đâu là cách tốt nhất để chuyển đổi cặp giá trị khóa được định dạng json thành băm ruby ​​với biểu tượng là khóa: ví dụ:

{ 'user': { 'name': 'foo', 'age': 40, 'location': { 'city' : 'bar', 'state': 'ca' } } }
==> 
{ :user=>{ :name => 'foo', :age =>'40', :location=>{ :city => 'bar', :state=>'ca' } } }

Có một phương pháp trợ giúp có thể làm điều này?


hãy thử cái này http://stackoverflow.com/a/43773159/1297435cho rails 4.1
rails_id

Câu trả lời:


256

bằng cách sử dụng đá quý json khi phân tích chuỗi json mà bạn có thể chuyển vào tùy chọn Symbolize_names. Xem tại đây: http://flori.github.com/json/doc/index.html (xem dưới phân tích cú pháp)

ví dụ:

>> s ="{\"akey\":\"one\",\"bkey\":\"two\"}"
>> JSON.parse(s,:symbolize_names => true)
=> {:akey=>"one", :bkey=>"two"} 

4
Nhân tiện, Ruby 1.9 bao gồm thư viện này.
Simon Perepelitsa

điều này đã không được sử dụng :symbolize_keys? tại sao tên đó lại thay đổi?
Lukas

5
@Lukas: symbolize_keyslà một thứ Rails.
wyattisimo


19

Leventix, cảm ơn bạn đã trả lời.

Phương thức Marshal.load (Marshal.dump (h)) có lẽ có tính toàn vẹn nhất trong số các phương thức khác nhau vì nó bảo toàn các kiểu khóa ban đầu một cách đệ quy .

Điều này quan trọng trong trường hợp bạn có một băm lồng nhau với hỗn hợp các khóa chuỗi và ký hiệu và bạn muốn bảo toàn hỗn hợp đó khi giải mã (ví dụ: điều này có thể xảy ra nếu băm của bạn chứa các đối tượng tùy chỉnh của riêng bạn ngoài thứ ba có độ phức tạp cao / lồng nhau -đối tượng bên có khóa mà bạn không thể thao tác / chuyển đổi vì bất kỳ lý do gì, như giới hạn thời gian của dự án).

Ví dụ:

h = {
      :youtube => {
                    :search   => 'daffy',                 # nested symbol key
                    'history' => ['goofy', 'mickey']      # nested string key
                  }
    }

Phương pháp 1 : JSON.parse - tượng trưng cho tất cả các khóa một cách đệ quy => Không bảo toàn hỗn hợp gốc

JSON.parse( h.to_json, {:symbolize_names => true} )
  => { :youtube => { :search=> "daffy", :history => ["goofy", "mickey"] } } 

Phương pháp 2 : ActiveSupport :: JSON.decode - chỉ tượng trưng cho các khóa cấp cao nhất => Không bảo toàn hỗn hợp gốc

ActiveSupport::JSON.decode( ActiveSupport::JSON.encode(h) ).symbolize_keys
  => { :youtube => { "search" => "daffy", "history" => ["goofy", "mickey"] } }

Phương pháp 3 : Marshal.load - bảo tồn hỗn hợp chuỗi / ký hiệu ban đầu trong các khóa lồng nhau. HOÀN HẢO!

Marshal.load( Marshal.dump(h) )
  => { :youtube => { :search => "daffy", "history" => ["goofy", "mickey"] } }

Trừ khi có một nhược điểm mà tôi không biết, tôi nghĩ Phương pháp 3 là cách để đi.

Chúc mừng


2
Ở đây không có gì đảm bảo rằng bạn có quyền kiểm soát phía bên kia, vì vậy tôi tin rằng bạn phải tuân theo định dạng JSON. Nếu bạn có toàn quyền kiểm soát cả hai bên, thì Marshal thực sự là một định dạng tốt, nhưng nó không phù hợp với mục đích chung.
ớn lạnh 42

5

Không có bất kỳ thứ gì được tích hợp sẵn để thực hiện thủ thuật này, nhưng không quá khó để viết mã để thực hiện nó bằng cách sử dụng đá quý JSON. Có một symbolize_keysphương thức được tích hợp trong Rails nếu bạn đang sử dụng phương thức đó, nhưng điều đó không tượng trưng cho các khóa đệ quy như bạn cần.

require 'json'

def json_to_sym_hash(json)
  json.gsub!('\'', '"')
  parsed = JSON.parse(json)
  symbolize_keys(parsed)
end

def symbolize_keys(hash)
  hash.inject({}){|new_hash, key_value|
    key, value = key_value
    value = symbolize_keys(value) if value.is_a?(Hash)
    new_hash[key.to_sym] = value
    new_hash
  }
end

Như Leventix đã nói, đá quý JSON chỉ xử lý các chuỗi được trích dẫn kép (điều này đúng về mặt kỹ thuật - JSON nên được định dạng bằng dấu ngoặc kép). Đoạn mã này sẽ làm sạch nó trước khi cố gắng phân tích cú pháp.


4

Phương pháp đệ quy:

require 'json'

def JSON.parse(source, opts = {})
  r = JSON.parser.new(source, opts).parse
  r = keys_to_symbol(r) if opts[:symbolize_names]
  return r
end

def keys_to_symbol(h)
  new_hash = {}
  h.each do |k,v|
    if v.class == String || v.class == Fixnum || v.class == Float
      new_hash[k.to_sym] = v
    elsif v.class == Hash
      new_hash[k.to_sym] = keys_to_symbol(v)
    elsif v.class == Array
      new_hash[k.to_sym] = keys_to_symbol_array(v)
    else
      raise ArgumentError, "Type not supported: #{v.class}"
    end
  end
  return new_hash
end

def keys_to_symbol_array(array)
  new_array = []
  array.each do |i|
    if i.class == Hash
      new_array << keys_to_symbol(i)
    elsif i.class == Array
      new_array << keys_to_symbol_array(i)
    else
      new_array << i
    end
  end
  return new_array
end

1

Tất nhiên, có một đá quý json , nhưng chỉ xử lý dấu ngoặc kép.


Như madlep nói dưới đây - đó là tất cả bạn cần nếu bạn biết rằng JSON sẽ có hiệu lực (ví dụ bạn đang làm điều đó cho mình!)
edavey

Điều này không hoạt động. JSON.parse(JSON.generate([:a])) # => ["a"]
Justin L.

2
Đó là bởi vì JSON không thể đại diện cho các ký hiệu. Bạn có thể sử dụng: Marshal.load(Marshal.dump([:a]))thay thế.
Leventix

1

Một cách khác để xử lý điều này là sử dụng tuần tự hóa / giải mã hóa YAML, cũng giữ nguyên định dạng của khóa:

YAML.load({test: {'test' => { ':test' => 5}}}.to_yaml) 
=> {:test=>{"test"=>{":test"=>5}}}

Lợi ích của cách tiếp cận này có vẻ như là một định dạng phù hợp hơn với các dịch vụ REST ...


Không bao giờ cho phép người dùng get đầu vào YAML.load: tenderlovemaking.com/2013/02/06/yaml-f7u12.html
Rafe

@Rafe ý bạn là lỗ hổng bảo mật của năm 2013 này vẫn chưa được khắc phục cho đến hôm nay?
bert bruynooghe

1
Các ký hiệu được GC'ed kể từ Ruby 2.2. YAML.loadcó nghĩa là để tuần tự hóa các đối tượng tùy ý (ví dụ: cho bộ nhớ cache). Các đề xuất YAML.safe_loadđã được giới thiệu một vài tháng sau đó bài viết trên blog, vì vậy nó là một vấn đề của việc sử dụng đúng đắn: github.com/ruby/psych/commit/...
Rafe

0

Cách thuận tiện nhất là sử dụng đá quý nice_hash: https://github.com/MarioRuiz/nice_hash

require 'nice_hash'
my_str = "{ 'user': { 'name': 'foo', 'age': 40, 'location': { 'city' : 'bar', 'state': 'ca' } } }"

# on my_hash will have the json as a hash
my_hash = my_str.json

# or you can filter and get what you want
vals = my_str.json(:age, :city)

# even you can access the keys like this:
puts my_hash._user._location._city
puts my_hash.user.location.city
puts my_hash[:user][:location][:city]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.