【问题标题】:What's the easiest way to make HTTParty return an object/class rather than a JSON response?使 HTTParty 返回对象/类而不是 JSON 响应的最简单方法是什么?
【发布时间】:2025-12-09 13:25:01
【问题描述】:

现在我打电话:

def child(parent_id, child_id, params = {})
  if @api_token
    self.class.get("/parents/#{parent_id}/children/#{child_id}", query: params,
                      :headers=>{"Authorization"=>"Token token=#{@api_token}"})
  else
    self.class.get("/parents/#{parent_id}/children/#{child_id}", query: params)
  end
end

它直接从 API 返回 JSON 响应作为哈希。我有没有一种简单的方法来标准化响应,以便它解析 JSON 并生成一个类?

例子:

Original Response
--------
{'child' : { 'name' : 'John Doe', 'age' : 23 } }

Desired Response
--------
res.name # John Doe
res.age  # 23
res.class # APIClient::Child

【问题讨论】:

  • 我在想也许可以使用 hashie。但是后来,我看到您要求res.class 返回根节点。 Hashie 在这里可能帮不上什么忙。我猜,也许您可​​以扩展 hashie 的 API 以使其满足您的需求?
  • @bsvin33t hashie 可能不是一个坏主意。我真的不需要 .class,只是认为它有效地说明了这个概念。

标签: ruby-on-rails ruby ruby-on-rails-4 httparty


【解决方案1】:

它可以通过传递给请求调用的自定义解析器来实现(但是我强烈建议不要这样做并保持现状)

您可以传递的解析器示例是

class InstanceParser < HTTParty::Parser
  def parse
    #assuming you always have a json in format { 'one_key_mapping_to_model' => data }        
    body_as_json = JSON.parse(body) #string parsed to json        

    model_class_name = body_as_json.keys.first # == 'one_key_mapping'       
    model_class_data = body_as_json[model_class_name] # == data
    class_instance = model_class_name.camelize.constantize.new(model_class_data) # will create new instance of OneKeyMapping

    class_instance
  end
end

然后在你的 api 调用中传递 self.class.get("/parents/#{parent_id}/children/#{child_id}", query: params, parser: InstanceParser)

【讨论】:

    【解决方案2】:

    将哈希传递给初始化器。

    class APIClient::Child
      attr_accessor :foo, :bar
    
      def initialize(hash = {})
        hash.each do |k,v|
          public_send("#{k}=", v)
        end
      end
    end
    

    然后在您的 API 客户端中,您将在响应和对象之间进行映射:

    def child(parent_id, child_id, params = {})
      opts = { query: params }
      opts.merge!(:headers=>{"Authorization"=>"Token token=#{@api_token}"}) if @api_token
       begin 
        res = self.class.get("/parents/#{parent_id}/children/#{child_id}", opts)
        case response.code
          when 200
            APIClient::Child.new(res[:child])
          when 404
            # not found - raise some sort of error that the API client consumer 
            # can catch maybe?
          else
            # probably just log it since there is little we can do here.
        end      
       rescue HTTParty::Error
         # probaly just log it. Could be a connection error or something else.
       end
    end
    

    这可能没有你所希望的那么神奇,但是如果不映射 HTTP 请求和适合消费的对象,API 客户端的作用是什么。当涉及到传递令牌和处理错误时,这里的大多数 boooing 样板代码都可以从父类或模块中转出。

    【讨论】:

    • 请注意,如果您并不真正关心声明和记录您创建的对象的属性,您可以简单地使用OpenStruct 或让您的APIClient::Child 扩展OpenStruct