lostisland/faraday · error · TypeError

Can't convert #{params.class} into Hash.

Error message

Can't convert #{params.class} into Hash.

What it means

Faraday::FlatParamsEncoder.encode turns request params into a query string. It accepts nil, an Array of [key, value] pairs, or any object responding to #to_hash; every other input raises TypeError. The most common violation is passing an already-encoded query String where a Hash is expected, because String responds to neither #to_hash nor is it an Array of pairs.

Source

Thrown at lib/faraday/encoders/flat_params_encoder.rb:29

    end

    # Encode converts the given param into a URI querystring. Keys and values
    # will converted to strings and appropriately escaped for the URI.
    #
    # @param params [Hash] query arguments to convert.
    #
    # @example
    #
    #   encode({a: %w[one two three], b: true, c: "C"})
    #   # => 'a=one&a=two&a=three&b=true&c=C'
    #
    # @return [String] the URI querystring (without the leading '?')
    def self.encode(params)
      return nil if params.nil?

      unless params.is_a?(Array)
        unless params.respond_to?(:to_hash)
          raise TypeError,
                "Can't convert #{params.class} into Hash."
        end
        params = params.to_hash
        params = params.map do |key, value|
          key = key.to_s if key.is_a?(Symbol)
          [key, value]
        end

        # Only to be used for non-Array inputs. Arrays should preserve order.
        params.sort! if @sort_params
      end

      # The params have form [['key1', 'value1'], ['key2', 'value2']].
      buffer = +''
      params.each do |key, value|
        encoded_key = escape(key)
        if value.nil?
          buffer << "#{encoded_key}&"

View on GitHub (pinned to b25b1b26cc)

Solutions

  1. Pass a Hash: conn.get('https://api.example.com/search', q: 'ruby', page: 2).
  2. If you already hold an encoded query string, append it to the URL yourself instead of the params slot: conn.get("https://api.example.com/search?#{qs}") or build a URI and set uri.query.
  3. Pass an Array of pairs when you need repeated keys with ordering: conn.get(url, [%w[q ruby], %w[q rails]]).
  4. For custom param objects, define #to_hash on the class (or call .to_h on the caller side) so the encoder can convert it.

Example fix

# before
qs = URI.encode_www_form(q: 'ruby', page: 2)
conn.get('https://api.example.com/search', qs)
# => TypeError: Can't convert String into Hash.

# after
conn.get('https://api.example.com/search', q: 'ruby', page: 2)
# or, with a pre-built query string, put it in the URL:
conn.get("https://api.example.com/search?#{qs}")
Defensive patterns

Strategy: type-guard

Validate before calling

params = nil unless params.is_a?(Hash) || params.is_a?(Array) || params.nil?
conn.get(url, params)

Type guard

def encodable_params?(params)
  params.nil? || params.is_a?(Array) || params.respond_to?(:to_hash)
end

Try / catch

begin
  conn.get(url, params)
rescue TypeError => e
  raise unless e.message.include?('into Hash')
  conn.get(url, params.to_h) # last-resort conversion
end

Prevention

When it happens

Trigger: Calling conn.get('https://api.example.com/search', 'q=ruby&page=2') — the second argument is params, not a query string; passing a JSON string, Integer, or arbitrary object as the params argument of get/post headers-level APIs; handing a custom config object to Faraday that does not implement #to_hash.

Common situations: Assuming the second argument to conn.get is a query string (it is a params Hash that gets encoded); porting code from libraries with a different signature; double-encoding bugs where the string then gets escaped again; passing a Struct or OpenStruct-like object that lacks #to_hash.

Related errors


AI-assisted analysis of lostisland/faraday@b25b1b26cc (2026-08-21). Data as JSON: /api/errors/f776472672d6ad98. Report an issue: GitHub.