Невозможно использовать точечный синтаксис для хэша 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)

Узнайте больше о классе Ruby Struct.

Почему бы и нет, вы можете сделать это с помощью метапрограммирования

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

Так как Hash не имеет version метод.

Другие вопросы по тегам