grafana/k6 · error
unmarshalling response body to JSON: %w
Error message
unmarshalling response body to JSON: %w
What it means
Response.JSON() fetches the body successfully but the bytes are not valid JSON: encoding/json's Unmarshal fails on the raw body. The response was retrieved fine; its content is simply not parseable JSON (HTML error page, plain text, empty or truncated payload, BOM-prefixed body).
Source
Thrown at internal/js/modules/k6/browser/common/http.go:770
r.extraHeadersMu.RUnlock()
return headers
}
// JSON returns the response body as JSON data.
func (r *Response) JSON() (any, error) {
if r.cachedJSON != nil {
return r.cachedJSON, nil
}
if err := r.fetchBody(); err != nil {
return nil, fmt.Errorf("getting response body: %w", err)
}
r.bodyMu.RLock()
defer r.bodyMu.RUnlock()
var v any
if err := json.Unmarshal(r.body, &v); err != nil {
return nil, fmt.Errorf("unmarshalling response body to JSON: %w", err)
}
r.cachedJSON = v
return v, nil
}
// Ok returns true if status code of response if considered ok, otherwise returns false.
func (r *Response) Ok() bool {
if r.status == 0 || (r.status >= 200 && r.status <= 299) {
return true
}
return false
}
// Request returns the request that led to this response.
func (r *Response) Request() *Request {
return r.request
}View on GitHub (pinned to 93accf6570)
Solutions
- Check res.status() and the response content-type header before calling res.json()
- For debugging, call res.text() and log the first ~200 bytes to see what actually came back
- Fix the upstream cause: re-authenticate, fix the URL, or handle the non-JSON branch explicitly
- If you must tolerate junk, wrap JSON.parse(res.text()) in your own try/catch instead of res.json()
Example fix
// before
const res = await page.goto('https://api.example.com/me');
const me = await res.json(); // throws on HTML login page
// after
const res = await page.goto('https://api.example.com/me');
const ct = (res.headers()['content-type'] || '');
if (!ct.includes('application/json')) {
throw new Error('expected JSON, got: ' + ct);
}
const me = await res.json(); Defensive patterns
Strategy: validation
Validate before calling
const ct = res.headers()['content-type'] || '';
if (!ct.includes('application/json')) {
throw new Error(`non-JSON response (${ct}): ${(await res.text()).slice(0, 120)}`);
}
const data = await res.json(); Try / catch
try { data = await res.json(); }
catch (e) { if (!/unmarshalling response body to JSON/.test(String(e.message))) throw e; /* handle non-JSON */ } Prevention
- Verify content-type before parsing
- Watch for HTML error/login pages after auth expiry
- On parse failure, log a text slice to see the actual payload
When it happens
Trigger: Calling res.json() on a response whose body is an HTML login/error page, plain text, empty, or truncated; endpoints returning JSON with a BOM or trailing junk; receiving a 200 page from a captive portal or WAF instead of the API.
Common situations: API returns HTML after session expiry or when unauthenticated; 500 error pages with content-type text/html; asserting content-type mismatches; proxies injecting banners into responses.
Related errors
- response body is unavailable for redirect responses
- decoding response: %w
- couldn't parse the configuration from %q: %w
- format of value must be k=v, received %q
- format of header must be header.k=v, received %q
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/ba1a9f4dfce9a6d0.
Report an issue: GitHub.