ruby-grape/grape · error · ArgumentError

coerce_with disallowed for type: JSON

Error message

coerce_with disallowed for type: JSON

What it means

The `JSON` type (and `Array[JSON]`, listed in ParamsScope::SPECIAL_JSON) is special: Grape already defines its own coercion for it by parsing the incoming string with the configured JSON backend. Supplying `coerce_with` for a JSON type would be redundant and ambiguous, so `check_coerce_with` raises ArgumentError at declaration time.

Source

Thrown at lib/grape/validations/params_scope.rb:375

        validate_presence(spec, attrs)

        # Coerce runs second — later validators see the typed value.
        validate_coerce(spec, attrs)

        spec.validator_entries.each do |type, options|
          validate(type, options, attrs, spec.required?, spec.shared_opts)
        end
      end

      # Enforce correct usage of :coerce_with on a CoerceOptions.
      # We do not allow coercion without a type, nor with +JSON+ as a type
      # since that defines its own coercion method.
      def check_coerce_with(coerce_options)
        return unless coerce_options.coerce_method
        raise ArgumentError, 'must supply type for coerce_with' unless coerce_options.type
        return unless SPECIAL_JSON.include?(coerce_options.type)

        raise ArgumentError, 'coerce_with disallowed for type: JSON'
      end

      def validate_presence(spec, attrs)
        return unless spec.required?

        validate('presence', spec.presence_options, attrs, true, spec.shared_opts)
      end

      def validate_coerce(spec, attrs)
        coerce_options = spec.coerce_options
        check_coerce_with(coerce_options)
        # Falsy check is intentional: when a remountable API is first evaluated
        # on its base instance (no configuration supplied yet),
        # configuration[:some_type] evaluates to nil. Skipping instantiation
        # here is correct — the real mounted instance will replay this step
        # with the actual type value.
        return unless coerce_options.type

View on GitHub (pinned to 22d7975629)

Solutions

  1. Remove `coerce_with` — `type: JSON` already parses the request value
  2. Keep custom parsing under a different type: `requires :payload, type: String, coerce_with: ->(raw) { Oj.load(raw) }`
  3. For structured custom types, use a custom type class with a class-level `parse` and pass it as `type:`

Example fix

# before
requires :payload, type: JSON, coerce_with: ->(raw) { Oj.load(raw) }

# after
requires :payload, type: JSON
Defensive patterns

Strategy: validation

Validate before calling

SPECIAL_JSON = [Grape::Validations::Types::JSON, Array[Grape::Validations::Types::JSON]].freeze

def assert_json_without_coercer!(opts)
  return unless opts[:coerce_with] && SPECIAL_JSON.include?(opts[:type])

  raise ArgumentError, 'do not pass coerce_with with JSON types; the type defines its own coercion'
end

Try / catch

begin
  requires :payload, type: JSON, coerce_with: ->(raw) { Oj.load(raw) }
rescue ArgumentError => e
  raise "JSON param must not carry coerce_with: #{e.message}"
end

Prevention

When it happens

Trigger: `requires :payload, type: JSON, coerce_with: ->(raw) { Oj.load(raw) }`; `requires :rows, type: Array[JSON], coerce_with: CustomParser`; any declaration whose resolved coercion type is `JSON` or `Array[JSON]` while a `coerce_with` method is present.

Common situations: Switching a `type: String` param with custom parsing over to `type: JSON` and forgetting to remove `coerce_with`; trying to plug a faster parser (Oj) per-param instead of configuring Grape's JSON backend globally.

Related errors


AI-assisted analysis of ruby-grape/grape@22d7975629 (2026-08-21). Data as JSON: /api/errors/48baca3cc6f270fa. Report an issue: GitHub.