fluent/fluentd · error · ConfigError

hash required but got #{val.inspect}

Error message

hash required but got #{val.inspect}

What it means

hash_value accepts a JSON object string (starting with '{'), a 'k1:v1,k2:v2' comma/colon string, or a pre-built Hash, and raises ConfigError when the result is not a Hash. In practice the error fires when a non-String, non-Hash object is passed: an Array, Numeric, Symbol, or arbitrary object (e.g. from programmatic configuration or a reformat with value_type), because string inputs go down the parse paths. The check is param.class != Hash, so even a Hash subclass (ActiveSupport::HashWithIndifferentAccess, Hashie::Mash) is rejected.

Source

Thrown at lib/fluent/config/types.rb:209

        when :time then Config.time_value(value, opts, name)
        when :regexp then Config.regexp_value(value, opts, name)
        when :symbol then Config.symbol_value(value, opts, name)
        else
          raise "unknown type in REFORMAT: #{type}"
        end
      end
    }

    def self.hash_value(val, opts = {}, name = nil)
      return nil if val.nil?

      param = if val.is_a?(String)
                val.start_with?('{') ? JSON.parse(val, Fluent::DEFAULT_JSON_PARSE_OPTIONS) : Hash[val.strip.split(/\s*,\s*/).map{|v| v.split(':', 2)}]
              else
                val
              end
      if param.class != Hash
        raise ConfigError, "hash required but got #{val.inspect}"
      end
      if opts.empty?
        param
      else
        newparam = {}
        param.each_pair do |key, value|
          new_key = opts[:symbolize_keys] ? key.to_sym : key
          newparam[new_key] = opts[:value_type] ? REFORMAT_VALUE.call(opts[:value_type], value, opts, new_key) : value
        end
        newparam
      end
    end

    HASH_TYPE = Proc.new { |val, opts = {}, name = nil|
      Config.hash_value(val, opts, name)
    }

    def self.array_value(val, opts = {}, name = nil)

View on GitHub (pinned to dd45c6e18d)

Solutions

  1. Pass a plain Hash (convert subclasses with to_h then re-wrap: Hash[obj.to_h]) or a 'k1:v1,k2:v2' string
  2. For JSON input, ensure the top-level document is an object and serialize it with JSON.generate(hash)
  3. When configuring programmatically, convert arrays to an index-keyed hash or change the param to :array
  4. In tests, prefer config strings ('{"k":"v"}') so the same parse path as production runs

Example fix

# before
plugin.configure(config_element('match', '**', { headers: ["a", "b"] }))

# after
plugin.configure(config_element('match', '**', { headers: 'a:1,b:2' }))
Defensive patterns

Strategy: type-guard

Validate before calling

def hash_param_ok?(v)
  return true if v.class == Hash
  v.is_a?(String) && (v.start_with?('{') || v.include?(':'))
end

Type guard

def hash_param_ok?(v)
  return true if v.class == Hash
  v.is_a?(String) && (v.start_with?('{') || v.include?(':'))
end

Try / catch

begin
  Fluent::Config.hash_value(val, opts, name)
rescue Fluent::ConfigError => e
  raise ConfigInvalid, "#{name} must be a hash or 'k:v,k:v' string: #{e.message}"
end

Prevention

When it happens

Trigger: Passing [1, 2] or a Symbol to a :hash param when building Fluent::Config::Element programmatically or in plugin tests; handing a JSON array string where an object string was expected; passing a Hash subclass instead of a plain Hash; REFORMAT_VALUE with value_type :hash receiving a scalar element.

Common situations: RSpec tests configuring plugins with Ruby values instead of config strings; plugins consuming parsed JSON where the document is an array, not an object; app code that assumed duck-typed Hashes work.

Related errors


AI-assisted analysis of fluent/fluentd@dd45c6e18d (2026-08-21). Data as JSON: /api/errors/d8cd03e27c7e4430. Report an issue: GitHub.