从/到JSON API的Ruby哈希对象化

igsr9ssn  于 2023-06-29  发布在  Ruby
关注(0)|答案(3)|浏览(109)

我刚刚发布了一个ruby gem来使用一些JSON over HTTP API:
https://github.com/solyaris/blomming_api
我的naif ruby代码只是将API端点(json_data)返回的复杂/嵌套JSON数据结构转换为ruby哈希(hash_data),在一个平面的一对一事务中(JSON到ruby哈希,反之亦然)。达特很好但是。。
我想一个编程接口更“高层次”。也许为每个端点都初始化一个类Resource,但我对智能实现感到困惑。
让我用一个抽象的代码来解释。
假设我有一个复杂的/嵌套的JSON,由API接收,通常是一个哈希数组,递归嵌套如下(想象的例子):

json_data = '[{
    "commute": {
        "minutes": 0,
        "startTime": "Wed May 06 22:14:12 EDT 2014",
        "locations": [
            {
                "latitude": "40.4220061",
                "longitude": "40.4220061"
            },
            {
                "latitude": "40.4989909",
                "longitude": "40.48989805"
            },            
            {
                "latitude": "40.4111169",
                "longitude": "40.42222869"
            }            
        ]
    }
},
{
    "commute": {
        "minutes": 2,
        "startTime": "Wed May 28 20:14:12 EDT 2014",
        "locations": [
            {
                "latitude": "43.4220063",
                "longitude": "43.4220063"
            }
        ]
    }
}]'

目前我所做的,当我收到一个类似的JSON表单时,API只是:

# from JSON to hash 
hash_data = JSON.load json_data

# and to assign values:
coords = hash_data.first["commute"]["locations"].last
coords["longitude"] = "40.00" # was "40.4111169"
coords["latitude"] = "41.00" # was "40.42222869"

这是好的,但有可怕/混乱的语法。相反,我可能会喜欢这样的东西:

# create object Resource from hash
res = Resource.create( hash_data )

# ... some processing

# assign a "nested" variables: longitude, latitude of object: res
coords = res.first.commute.locations.last
coords.longitude = "40.00" # was "40.4111169"
coords.latitude = "41.00" # was "40.42222869"

# ... some processing

# convert modified object: res into an hash again:
modified_hash = res.save

# and probably at least I'll recover to to JSON:
modified_json = JSON.dump modified_hash

我读了一些有趣的帖子:http://pullmonkey.com/2008/01/06/convert-a-ruby-hash-into-a-class-object/http://www.goodercode.com/wp/convert-your-hash-keys-to-object-properties-in-ruby/
并复制Kerry Wilson的代码,我在下面概述了实现:

class Resource

  def self.create (hash)
    new ( hash)
  end

  def initialize ( hash)
    hash.to_obj
  end

  def save
    # or to_hash() 
    # todo! HELP! (see later)
  end

end

class ::Hash
  # add keys to hash
  def to_obj
    self.each do |k,v|

      v.to_obj if v.kind_of? Hash
      v.to_obj if v.kind_of? Array

      k=k.gsub(/\.|\s|-|\/|\'/, '_').downcase.to_sym

      ## create and initialize an instance variable for this key/value pair
      self.instance_variable_set("@#{k}", v)

      ## create the getter that returns the instance variable
      self.class.send(:define_method, k, proc{self.instance_variable_get("@#{k}")})

      ## create the setter that sets the instance variable
      self.class.send(:define_method, "#{k}=", proc{|v| self.instance_variable_set("@#{k}", v)})
    end
    return self
  end
end

class ::Array
  def to_obj
    self.map { |v| v.to_obj }
  end 
end
#------------------------------------------------------------

顺便说一句,我研究了一点ActiveResource项目(如果我理解得很好,它是Rails的一部分)。对于我的范围来说,战神可能很棒,但问题是ARes对完整的REST API的假设有点过于“严格”。在我的例子中,服务器API并不像战神期望的那样完全是RESTfull的……总而言之,我会做很多工作来子类化/修改战神行为,目前我放弃了使用ActiveResource的想法
问题:
1.有人可以帮助我实现上面代码的save()方法(我真的不擅长递归方法...:-()?
1.是否存在一些宝石,以上述素描hash_to_object()object_to_hash()翻译?
1.您如何看待通过http API对来自JSON的“任意”散列进行“自动”对象化?我的意思是:我看到了一个伟大的优点,我不需要客户端静态数据结构,允许灵活地适应可能的服务器端变化。但另一方面,做这个自动objectify,有一个可能的缺点的副作用,让安全问题...比如恶意JSON注入(可能是不可信的通信网络……)
你对这一切有什么看法?欢迎任何建议!很抱歉我的长文和我的ruby语言元编程危害:-)
更新2:我仍然有兴趣阅读关于问题3的意见:优点/缺点:为每个收到的JSON创建Resource类优点/缺点:创建静态(抢占式属性)/自动/动态嵌套对象
更新1:回复Simone:谢谢,你是对的,Mash有一个很好的.to_hash()方法:

require 'json'
require 'hashie'

json_data = '{
    "commute": {
        "minutes": 0,
        "startTime": "Wed May 06 22:14:12 EDT 2014",
        "locations": [
            {
                "latitude": "40.4220061",
                "longitude": "40.4220061"
            },
            {
                "latitude": "40.4989909",
                "longitude": "40.48989805"
            },            
            {
                "latitude": "40.4111169",
                "longitude": "40.42222869"
            }            
        ]
    }
}'

# trasforma in hash
hash = JSON.load json_data

puts hash

res = Hashie::Mash.new hash

# assign a "nested" variables: longitude, latitude of object: res
coords = res.commute.locations.last
coords.longitude = "40.00" # was "40.4111169"
coords.latitude = "41.00" # was "40.42222869"

puts; puts "longitude: #{res.commute.locations.last.longitude}"
puts "latitude: #{res.commute.locations.last.latitude}"

modified_hash = res.to_hash
puts; puts modified_hash
sauutmhj

sauutmhj1#

这个特性是由一些gem提供的。其中最著名的是Hashie,特别是Hashie::Mash类。
Mash是一个扩展的Hash,它提供了简单的伪对象功能,可以从哈希构建并轻松扩展。它被设计用于RESTful API库中,以提供对JSON和XML解析散列的简单对象式访问。
Mash还支持多级对象。

des4xlb0

des4xlb02#

根据您的需要和嵌套级别,您可以使用OpenStruct
我正在使用一个简单的测试存根。Hashie本来可以很好地工作,但它是一个比我需要的更大的工具(并增加了依赖性)。

toe95027

toe950273#

最近的ruby版本(至少2.3,可能更早)允许直接解析成OpenStruct。
给定问题中的json_data:

commute_data = JSON.parse json_data, {object_class: OpenStruct}
commute_data.first.commute.locations.last.latitude

#=> "40.4111169"

json_data被解析为一个带有访问器的OpenStruct对象,而不需要向项目中添加依赖项。

相关问题