opf/openproject · error · JSON::ParserError

Filter must be a JSON object, got #{filter.class}

Error message

Filter must be a JSON object, got #{filter.class}

What it means

API::V3::ParseQueryParamsService parses the filters parameter of API v3 work package queries. After JSON.parsing the string, each element must be a Hash with exactly one attribute key mapping to an object with 'operator' and 'values'; any other shape raises JSON::ParserError with this class-revealing message. The service rescues it in json_parsed_params and returns a failed ServiceResult, so API clients see it as an error payload on the request.

Source

Thrown at app/services/api/v3/parse_query_params_service.rb:139

      #       "values": ["values", "for the", "operation"]
      #     }
      #   },
      #   { /* more filters if needed */}
      # ]
      def filters_from_params(params)
        filters = params[:filters] || params[:filter]
        return unless filters

        filters = JSON.parse filters if filters.is_a? String

        filters.map do |filter|
          filter_from_params(filter)
        end
      end

      def filter_from_params(filter)
        unless filter.is_a?(Hash)
          raise JSON::ParserError, "Filter must be a JSON object, got #{filter.class}"
        end

        attribute = filter.keys.first # there should only be one attribute per filter
        operator =  filter[attribute]["operator"]
        values = Array(filter[attribute]["values"])
        ar_attribute = convert_filter_attribute attribute, append_id: true

        { field: ar_attribute,
          operator:,
          values: }
      end

      def columns_from_params(params)
        columns = params_value(params, KEYS_COLUMNS)

        return unless columns

        columns.map do |column|

View on GitHub (pinned to d9742c43f3)

Solutions

  1. Send filters as an array of single-key objects: [{"status":{"operator":"=","values":["1"]}}] (URL-encoded)
  2. Wrap a single filter in an array — a bare object is the most common mistake
  3. Validate the payload shape client-side before sending: every element is an object with one key whose value has 'operator' and 'values'
  4. Read the class name in the error message ('got Array' / 'got String') to see which shape actually arrived, then compare with the documented example above the filters_from_params method

Example fix

# before — both raise "Filter must be a JSON object, got ..."
GET /api/v3/work_packages?filters={"status":{"operator":"=","values":["1"]}}
GET /api/v3/work_packages?filters=[["status","=","1"]]

# after — array of single-key objects, URL-encoded
GET /api/v3/work_packages?filters=%5B%7B%22status%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%221%22%5D%7D%7D%5D
Defensive patterns

Strategy: validation

Validate before calling

# build and check the filters payload before the request
filters = [{ "status" => { "operator" => "=", "values" => ["1"] } }]
unless valid_api_v3_filters?(filters)
  raise ArgumentError, "filters must be an array of single-key objects with operator/values"
end
JSON.generate(filters)

Type guard

# Ruby: validates the parsed shape the service expects
valid_api_v3_filters?(parsed)
  parsed.is_a?(Array) && parsed.all? do |f|
    f.is_a?(Hash) && f.size == 1 && f.values.first.is_a?(Hash) &&
      f.values.first.key?("operator") && f.values.first.key?("values")
  end
end

Prevention

When it happens

Trigger: Calling an endpoint that uses this service (e.g. GET /api/v3/work_packages or POST /api/v3/work_packages/form) with filters as a single JSON object ('{"status":{...}}' — Hash#map then yields Array pairs), as nested arrays ('[["status","=","1"]]'), or as an array of strings ('["status"]'). A JSON string that fails JSON.parse at line 130 raises the standard JSON::ParserError instead.

Common situations: Migrating clients from old nested-array filter syntax to the v3 object form; hand-built query strings that forget the outer array around a single filter; forgetting to URL-encode brackets; copy-pasting a single filter object from API docs instead of the array wrapper.

Related errors


AI-assisted analysis of opf/openproject@d9742c43f3 (2026-08-21). Data as JSON: /api/errors/908df4dad35a8945. Report an issue: GitHub.