aio-libs/aiohttp · error · ValueError

only one of data, text, or body should be specified

Error message

only one of data, text, or body should be specified

What it means

Raised by `aiohttp.web.json_response` when the caller passes both `data=` (the value to be JSON-encoded) and `text=` or `body=`. json_response encodes `data` into a JSON string and assigns it to text; passing text/body too creates an ambiguous source, so it is rejected.

Solutions

  1. Pass exactly one: `data=` for objects to encode, `text=` for an already-encoded JSON string, `body=` for already-encoded bytes.
  2. Use `json_response(data=obj)` in the normal case and let json_response serialize.
  3. For orjson (bytes output) use `aiohttp.web.json_response(data=obj, dumps=lambda d: orjson.dumps(d))` won't work — use the dedicated bytes variant `json_response_with_bytes` or build Response(body=...) directly.
  4. Audit helper signatures to ensure mutually-exclusive kwargs.

Example fix

// before
return json_response(data=payload, text=json.dumps(payload))

// after
return json_response(data=payload)  # json_response does the encoding
Defensive patterns

Strategy: validation

Validate before calling

def safe_json_response(data=sentinel, text=None, body=None, **kw):
    if data is not sentinel:
        assert not (text or body), 'pass only one of data, text, or body'
    return json_response(data=data, text=text, body=body, **kw)

Prevention

When it happens

Trigger: Calling `json_response(data=obj, text='literal')` or `json_response(data=obj, body=b'raw')`. Also triggered by helper wrappers that forward *args/**kwargs and accidentally pass both.

Common situations: Conditional handlers that sometimes pre-serialize and sometimes pass the raw object; refactoring that left both arguments wired; copy-pasting a Response() call into json_response() without dropping text/body.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/f9d77632386f4a95. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/web_response.py:752

        )
        self._headers[hdrs.CONTENT_ENCODING] = coding.value
        self._headers[hdrs.CONTENT_LENGTH] = str(len(self._compressed_body))


def json_response(
    data: Any = sentinel,
    *,
    text: str | None = None,
    body: bytes | None = None,
    status: int = 200,
    reason: str | None = None,
    headers: LooseHeaders | None = None,
    content_type: str = "application/json",
    dumps: JSONEncoder = json.dumps,
) -> Response:
    if data is not sentinel:
        if text or body:
            raise ValueError("only one of data, text, or body should be specified")
        else:
            text = dumps(data)
    return Response(
        text=text,
        body=body,
        status=status,
        reason=reason,
        headers=headers,
        content_type=content_type,
    )


def json_bytes_response(
    data: Any = sentinel,
    *,
    dumps: JSONBytesEncoder,
    body: bytes | None = None,
    status: int = 200,

View on GitHub (pinned to d041d4d0fd)