Ga naar hoofdinhoud

cnFetchJson

cnFetch plus JSON parsing and error normalisation. The everyday HTTP call for Conduction Nextcloud apps (ADR-071 Decision 1).

import { cnFetchJson, CnHttpError } from '@conduction/nextcloud-vue'

const data = await cnFetchJson('/apps/openregister/api/objects/myreg/myschema', {
query: { limit: 20 },
})
console.log(data.total, data.results)

Signature​

cnFetchJson(url, options?) => Promise<*>

Takes the same arguments as cnFetch. Returns the parsed body, or null for an empty body. Throws CnHttpError on any non-2xx.

Three behaviours worth knowing​

An empty body resolves to null, it does not throw​

DELETE endpoints in this stack answer 204. Making the happy path throw teaches callers to wrap everything in try/catch — and that is how real errors get swallowed.

await cnFetchJson(`/apps/openregister/api/objects/${reg}/${schema}/${id}`, {
method: 'DELETE',
}) // → null

Failures carry the status, not just a message​

A bare Error(message) loses the status — the single most useful fact about a failed request — and callers then re-parse the message string to recover it. That is how a 404-vs-403 distinction gets lost.

try {
await cnFetchJson('/apps/myapp/api/thing/123')
} catch (e) {
if (e instanceof CnHttpError && e.status === 403) {
// branch on the status directly
}
}

The body is read once​

A Response body is a stream. Calling .json() again on the error path throws "body stream already read" and masks the real HTTP error, so the body is read a single time via text() and reused for both paths. A non-JSON body (an HTML error page behind a 500, say) is preserved as raw text on error.body rather than becoming a parse error that hides the status.

See also​