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
- Pass a plain Hash (convert subclasses with to_h then re-wrap: Hash[obj.to_h]) or a 'k1:v1,k2:v2' string
- For JSON input, ensure the top-level document is an object and serialize it with JSON.generate(hash)
- When configuring programmatically, convert arrays to an index-keyed hash or change the param to :array
- 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
- Pass plain Hashes (Hash[obj.to_h] for subclasses) or JSON object strings
- In tests, configure with strings so production parse paths run
- Validate parsed JSON top-level type before feeding a :hash param
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
- array required but got #{val.inspect}
- Unknown #{@kind} plugin '#{type}'. Run 'gem search -rd fluen
- Missing '@type' parameter on <#{e.name}> directive
- 'format' parameter is required
- both of utc and localtime are specified, use only one of the
AI-assisted analysis of fluent/fluentd@dd45c6e18d (2026-08-21).
Data as JSON: /api/errors/d8cd03e27c7e4430.
Report an issue: GitHub.