Beta
How the generated TypeScript client types and reads a response whose success status declares more than one content type.
An OpenAPI operation can declare more than one content type for a single
success response, or a different content type on each status code — a
download endpoint returning either a JSON error summary or the raw file, for
example. The generated client types the reply as the union of every declared
variant, and reads the response body by the Content-Type header it actually
receives, not by the status code or by guessing.
Every success response's content map is classified per media type, across
every success status the operation declares:
application/json, text/json, or any +json suffix)
classifies as json and is modeled — the reply type carries your schema.text/*, or a handful of textual application/* types such
as application/xml) classifies as text and reads as a plain string.binary and reads as a Blob.An operation with exactly one variant is unaffected: its reply type and
generated code are exactly what they were before content types were
classified at all. An operation with more than one variant gets a union reply
type — but not only of the variants it declares. The runtime dispatch below
can hand back Blob or string for a Content-Type no declared variant
named, so a multi-variant reply type always includes both, whatever subset of
json/text/binary the operation actually declares:
// application/json and application/octet-stream both declared on 200
async function downloadReport(context: DownloadReportContext): Promise<HttpClientResponse<Report | string | Blob>>
// application/json on 200, image/png on 202
async function getRenderedReport(context: GetRenderedReportContext): Promise<HttpClientResponse<Report | string | Blob>>
// application/json and text/plain both declared, no binary variant declared at all
async function getNote(context: GetNoteContext): Promise<HttpClientResponse<Note | string | Blob>>
text/event-stream keeps its own precedence, unchanged: an operation offering
it becomes a streaming, async-iterable operation exactly as it already does,
and never joins a content-type union.
Typing the reply as a union only says what the response might be. Reading
it correctly is a runtime decision, made from the response's own
Content-Type header — the header states what the server actually sent,
which is what the client trusts over the declaration:
Content-Type reads via response.json().Content-Type reads via response.text().response.blob().This holds even for a Content-Type the specification never declared at
all, in either direction: a server sending image/png for an operation that
only declared application/octet-stream still gets read as binary, and a
server sending application/json for an operation that only declared text
and binary variants still gets parsed as JSON. The actual Content-Type —
not the declared one — always decides which reader runs. A response with no
Content-Type header at all falls back to the operation's
highest-priority declared variant, in the order above (JSON, then text, then
binary).
An operation that declares json and text but no binary variant at all
still reads a genuinely unrecognized Content-Type as a Blob, never as
text: reading an unrecognized body as text would corrupt it if it turns out
to be binary bytes, while reading a genuinely textual body as a Blob only
inconveniences the caller, who still has the exact bytes intact. Symmetrically,
an operation that declares json and binary but no text variant still
reads a genuinely textual, undeclared Content-Type as a string, not a
Blob — the dispatch classifies by the header's actual shape, not by what
the operation happened to declare.
Because of this, both data and rawData are typed to admit all three body
shapes — the JSON object (or modeled type), string, and Blob — for any
operation with more than one declared variant, whatever subset of them it
actually declares; the runtime dispatch can produce any of the three, so the
declared type always covers what it can actually assign. A single declared
variant is unaffected — its one reader is the only one the dispatch can ever
reach, so its reply type stays exactly that one type.
Narrow the union the way you would any other one:
const result = await client.downloadReport({ parameters: { reportId } })
if (result.data instanceof Blob) {
// an undeclared or declared binary variant
} else if (typeof result.data === 'string') {
// an undeclared or declared textual variant
} else {
// the modeled JSON variant
}
rawData holds the same value as data before any cast: the parsed JSON
object for the JSON variant, or the same string/Blob value data already
holds for the others — there is no separate "raw" form to keep for a body
that was never deserialized in the first place. rawData's declared type
differs from data's only in the JSON member — Record<string, any> versus
the modeled type — since data's JSON member is data cast from rawData.
Before per-variant classification, an operation's response content type was
resolved once for the whole operation, and a JSON variant declared anywhere —
even on an unrelated status code — made every other declared variant
invisible. An operation returning a binary file alongside a JSON error body
was generated as pure JSON, so a successful binary response was parsed with
response.json() and failed. Declaring a caller-side Accept header to pick
one variant was considered and rejected: a server that ignores Accept would
still send whatever it sends, and the client would still need to read it by
what actually came back.