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
- Send filters as an array of single-key objects: [{"status":{"operator":"=","values":["1"]}}] (URL-encoded)
- Wrap a single filter in an array — a bare object is the most common mistake
- Validate the payload shape client-side before sending: every element is an object with one key whose value has 'operator' and 'values'
- 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
- Generate filters programmatically (JSON.generate of Ruby hashes) instead of string concatenation — this prevents both shape and encoding mistakes
- Keep one canonical filter builder shared by all API clients
- Remember the exact contract: array of objects, one key each, value has operator + values
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
- AuthProvider with slug: "#{slug}" has not been found
- Invalid CF type
- Failed to create custom field '%{name}': %{message}
- Couldn't find WeekDay with day #{day}
- Failed to parse Jira API response: %{message}
AI-assisted analysis of opf/openproject@d9742c43f3 (2026-08-21).
Data as JSON: /api/errors/908df4dad35a8945.
Report an issue: GitHub.