Невозможно использовать точечный синтаксис для хэша ruby
Я использую net/http
получить некоторые данные JSON из API Yahoo Placemaker. После получения ответа я выполняю JSON.parse
на ответ. Это дает мне хэш, который выглядит так:
{"processingTime"=>"0.001493", "version"=>"1.4.0.526 build 111113", "documentLength"=>"25", "document"=>{"administrativeScope"=>{"woeId"=>"2503863", "type"=>"Town", "name"=>"Tampa, FL, US", "centroid"=>{"latitude"=>"27.9465", "longitude"=>"-82.4593"}}, "geographicScope"=>{"woeId"=>"2503863", "type"=>"Town", "name"=>"Tampa, FL, US", "centroid"=>{"latitude"=>"27.9465", "longitude"=>"-82.4593"}}, "localScopes"=>{"localScope"=>{"woeId"=>"2503863", "type"=>"Town", "name"=>"Tampa, FL, US (Town)", "centroid"=>{"latitude"=>"27.9465", "longitude"=>"-82.4593"}, "southWest"=>{"latitude"=>"27.8132", "longitude"=>"-82.6489"}, "northEast"=>{"latitude"=>"28.1714", "longitude"=>"-82.2539"}, "ancestors"=>[{"ancestor"=>{"woeId"=>"12587831", "type"=>"County", "name"=>"Hillsborough"}}, {"ancestor"=>{"woeId"=>"2347568", "type"=>"State", "name"=>"Florida"}}, {"ancestor"=>{"woeId"=>"23424977", "type"=>"Country", "name"=>"United States"}}]}}, "extents"=>{"center"=>{"latitude"=>"27.9465", "longitude"=>"-82.4593"}, "southWest"=>{"latitude"=>"27.8132", "longitude"=>"-82.6489"}, "northEast"=>{"latitude"=>"28.1714", "longitude"=>"-82.2539"}}, "placeDetails"=>{"placeId"=>"1", "place"=>{"woeId"=>"2503863", "type"=>"Town", "name"=>"Tampa, FL, US", "centroid"=>{"latitude"=>"27.9465", "longitude"=>"-82.4593"}}, "placeReferenceIds"=>"1", "matchType"=>"0", "weight"=>"1", "confidence"=>"8"}, "referenceList"=>{"reference"=>{"woeIds"=>"2503863", "placeReferenceId"=>"1", "placeIds"=>"1", "start"=>"15", "end"=>"20", "isPlaintextMarker"=>"1", "text"=>"Tampa", "type"=>"plaintext", "xpath"=>""}}}}
Я могу получить доступ к элементам, делая такие вещи, как jsonResponse['version']
но я не могу сделать jsonResponse.version
, Почему это?
10 ответов
Hash
не имеет точечного синтаксиса для своих ключей. OpenStruct
делает:
require 'ostruct'
hash = {:name => 'John'}
os = OpenStruct.new(hash)
p os.name #=> "John"
OpenStruct будет хорошо работать для чистого хэша, но для хэшей с встраиваемыми массивами или другими хэшами синтаксис точки будет подавлен. Я наткнулся на это решение, которое хорошо работает без загрузки в другой драгоценный камень: https://coderwall.com/p/74rajw/convert-a-complex-nested-hash-to-an-object основные шаги:
data = YAML::load(File.open("your yaml file"))
json_data = data.to_json
mystr = JSON.parse(json_data,object_class: OpenStruct)
Теперь вы можете получить доступ ко всем объектам в Mystr, используя точечный синтаксис.
Драгоценный камень HashDot будет работать для этого.
HashDot позволяет использовать синтаксис точечной нотации для хешей. Он также работает со строками json, которые были повторно проанализированы JSON.parse
,
require 'hash_dot'
hash = {b: {c: {d: 1}}}.to_dot
hash.b.c.d => 1
json_hash = JSON.parse(hash.to_json)
json_hash.b.c.d => 1
Если вы не хотите устанавливать какие-либо драгоценные камни, вы можете попробовать использовать нативный Ruby Struct
класс и некоторые трюки на Ruby, такие как оператор splat.
# regular hashes
customer = { name: "Maria", age: 21, country: "Brazil" }
customer.name
# => NoMethodError: undefined method `name' for {:name=>"Maria", :age=>21, :country=>"Brazil"}:Hash
# converting a hash to a struct
customer_on_steroids = Struct.new(*customer.keys).new(*customer.values)
customer_on_steroids.name
#=> "Maria"
Обратите внимание, что это простое решение работает только для одноуровневых хэшей. Сделать его динамичным и полностью функциональным для любого Hash
вам придется сделать это рекурсивным для создания подструктур внутри вашей структуры.
Вы также можете хранить Struct, как если бы это был класс.
customer_1 = { name: "Maria", age: 21, country: "Brazil" }
customer_2 = { name: "João", age: 32, country: "Brazil" }
customer_3 = { name: "José", age: 43, country: "Brazil" }
Customer = Struct.new(*customer_1.keys)
customer_on_steroids_1 = Customer.new(*customer_1.values)
customer_on_steroids_2 = Customer.new(*customer_2.values)
customer_on_steroids_3 = Customer.new(*customer_3.values)
Почему бы и нет, вы можете сделать это с помощью метапрограммирования
module LookLikeJSON
def method_missing(meth, *args, &block)
if has_key?(meth.to_s)
self[meth.to_s]
else
raise NoMethodError, 'undefined method #{meth} for #{self}'
end
end
end
h = {"processingTime"=>"0.001493", "version"=>"1.4.0.526 build 111113", "documentLength"=>"25"}
h.extend(LookLikeJSON)
h.processingTime #=> "0.001493"
Это функция JavaScript, а не функция Ruby. В Ruby для использования "точечного синтаксиса" объект должен отвечать на эти методы. Руби хэши используют #[](key)
метод доступа к элементам.
Я собираюсь пойти дальше и использовать @whodabudda с этим одним вкладышем
# example hash
hash = { some: [ {very: :deep}, {very: :nested}, {very: :hash} ] }
#one-liner to convert deep open
deep_open = JSON.parse(hash.to_json, object_class: OpenStruct)
#now you can do this sorcery!
deep_open.some.map(&:very)
=> ["deep", "nested", "hash"]
Если круговой обход JSON кажется слишком накладным:
# Constructor like OpenStruct, but not open; and deep, not shallow
# Example: hash = { a: 1, b: { c: 2, d: 3 } }
# obj = DeepStruct.new(hash)
# obj.a # read
# obj.b.c += 1 # read and write
# obj.x # error
class DeepStruct
def initialize(hash)
hash.each do |k, v|
self.class.send(:attr_accessor, k)
instance_variable_set("@#{k}", v.is_a?(Hash) ? DeepStruct.new(v) : v)
end
end
end
Если это в Rspec, заглушки тоже будут работать.
let(:item) { stub(current: 1, total: 1) }