skip to content

In OpenAPI, how do you describe an object-valued query parameter, and when is deepObject the wrong choice?

level: seniorimportance: should knowfreq 40%

answer

  1. A query string is flat, objects are not
  2. One option loses the parameter name
  3. Brackets keep the name but not nesting
  4. The escape hatch is a media type
  5. Check the server's parser first

basics

~20 s

OpenAPI offers three ways: style form with explode true, which flattens the object into top-level keys; style deepObject, which produces bracketed keys like filter[status]=open; and a content-typed parameter carrying the value as JSON. deepObject fails for nested objects and arrays.

solid answer

~40 s

An object in a query string has three encodings in OpenAPI 3.x. `style: form, explode: true` flattens the properties into top-level keys — `?R=100&G=200` — which loses the parameter name and collides with any other parameter of the same name. `style: deepObject, explode: true` keeps the name as a prefix: `?filter[status]=open&filter[owner]=me`. It is the readable option, but it is defined only for flat objects — nesting and arrays inside the object have no portable meaning, and tooling disagrees about them. The third option replaces `schema` with `content`, usually `application/json`, so the value is one percent-encoded JSON document. That handles arbitrary nesting and is unambiguous, at the price of an opaque URL that is awkward to hand-write and to cache-key. Choose `deepObject` for shallow filters and `content` when the structure is genuinely nested.

code

yaml · 30 lines
yaml
parameters:
  - name: filter
    in: query
    description: Flat filter - filter[status]=open&filter[assignee]=me
    style: deepObject
    explode: true
    schema:
      type: object
      properties:
        status:
          type: string
        assignee:
          type: string
  - name: criteria
    in: query
    description: Nested value carried as percent-encoded JSON
    content:
      application/json:
        schema:
          type: object
          properties:
            range:
              type: object
              properties:
                from:
                  type: string
                  format: date
                to:
                  type: string
                  format: date

go deeper

for a junior

Recall that an object cannot go into a query string without a rule, and that OpenAPI has to be told which of several bracket or comma conventions is meant.

for a middle

Explain what each of the three encodings produces on the wire and why form with explode: true loses the parameter name.

for a senior

Demonstrate that you verify the server framework's parser first, keep deepObject to flat objects, and reach for content: application/json or a request body once the structure nests.

for a principal

Own the estate-wide convention for filter parameters so that clients generated from different services do not each guess a different bracket grammar, and weigh URL length and cacheability against expressiveness.

## Why an object in a query string is awkward A query string is a flat list of name/value pairs. Anything with structure has to be projected onto that list, and there is no single standard for doing it — Rails, PHP, Spring and Express all parse slightly different conventions. OpenAPI 3.x gives you three declarations, and the interview question is really about which one you pick and what each costs. ## Option 1 — form with explode: true This is the default for query parameters, so it is what you get if you write an object schema and no serialization keywords. Each property becomes its own top-level key. For a parameter named `color` with value `{"R": 100, "G": 200, "B": 150}`, the URL is `?R=100&G=200&B=150`. The parameter's own name disappears from the wire entirely. That has two consequences: you cannot tell which object a key belonged to, and two different object parameters sharing a property name collide irrecoverably. It is acceptable only when the object is really just a shorthand for a set of independent flat parameters — and in that case you may as well declare them as separate parameters, which documents better. ## Option 2 — form with explode: false The same object becomes `color=R,100,G,200,B,150`: keys and values interleaved in one comma list. It preserves the parameter name but is unreadable, brittle if any value contains a comma, and supported inconsistently by server frameworks. It exists for completeness; it is rarely the right pick. ## Option 3 — deepObject `style: deepObject` with `explode: true` produces bracketed keys: `?color[R]=100&color[G]=200&color[B]=150`, or in the more typical case `?filter[status]=open&filter[assignee]=me`. This reads well, keeps the parameter name, and matches what several popular frameworks already parse. Its limits matter: - It is defined **only for objects** — not arrays, not primitives. - Its behaviour for **nested** objects and for arrays inside the object is not portably defined, and generators and validators differ. If your filter has `filter[range][from]` or `filter[tags][0]`, you are outside what the specification pins down and you are betting on one particular tool's interpretation. - The brackets must be percent-encoded in a strictly conforming URL, and tools differ about whether they do so. So `deepObject` is right for a **flat** bag of scalar properties, and wrong the moment the object nests. ## Option 4 — a content-typed parameter A Parameter Object may use `content` **instead of** `schema` — the two are mutually exclusive, and the `content` map must contain exactly one entry. The usual choice is `application/json`. The client serializes the value as JSON and percent-encodes it, giving `?filter=%7B%22status%22%3A%22open%22%7D`. This is the only option that expresses arbitrary nesting without ambiguity, because the serialization is a real, specified format rather than a projection onto flat keys. The costs are real too: the URL is unreadable and unpleasant to construct by hand or in a browser address bar; it is long, and long query strings run into proxy and server URL length limits; caching keys become sensitive to JSON key ordering and whitespace; and some older tooling handles `content` on parameters poorly compared to `schema`. ## Choosing A practical rule: if the object is flat and small, use `deepObject` and keep the parameter name in the brackets so the URL stays legible. If the structure genuinely nests — a filter grammar, a range object, a list of criteria — use `content: application/json` and accept the opaque URL, or reconsider whether the operation should take a body instead. If you find yourself relying on `deepObject` with nested brackets, you have left the part of the spec that different tools agree on, and the symptom will be a generated client whose URL the server silently parses into a partly-empty object. ## What to verify before writing it down Whichever you pick, check what the **server framework actually parses** first. The OpenAPI document is a description; it does not change the parser. The failure mode is quiet: the request succeeds, the filter object arrives half-populated, and the endpoint returns the unfiltered collection.

  • What happens to the parameter name when an object query parameter uses style form with explode true?
    It disappears from the URL. Each property becomes a top-level key, so `color` holding `{R:100,G:200}` is sent as `?R=100&G=200`. Two object parameters that share a property name then collide with no way to tell them apart, which is why this default is a poor fit for anything but a flat bag of independent values that would read better as separate parameters anyway.
  • Can a parameter declare both schema and content?
    No — they are mutually exclusive, and a `content` map on a parameter must contain exactly one entry. `schema` plus `style`/`explode` describes a value projected onto the URL grammar; `content` describes a value serialized in a named media type. A document with both is invalid, and linters report it.
  • When would you move a complex query object into a request body instead?
    When the structure has outgrown a URL: deep nesting, long lists, or values that push the query string toward proxy length limits. The tradeoff is that the operation stops being a cacheable, bookmarkable GET. Some teams keep GET for the simple case and add a separate search operation that takes a body for the complex one, describing both in the same document.

saying these in an interview costs you the question

  • Claims deepObject supports arbitrary nesting
  • Thinks the parameter name survives form with explode true
  • Uses schema and content on the same parameter
  • Assumes every framework parses bracketed keys the same way
  • Writes the object style without checking the server parser

context