{"record":{"id":"ba1a9f4dfce9a6d0","repo":"grafana/k6","slug":"unmarshalling-response-body-to-json-w","errorCode":null,"errorMessage":"unmarshalling response body to JSON: %w","messagePattern":"unmarshalling response body to JSON: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/common/http.go","lineNumber":770,"sourceCode":"\tr.extraHeadersMu.RUnlock()\n\treturn headers\n}\n\n// JSON returns the response body as JSON data.\nfunc (r *Response) JSON() (any, error) {\n\tif r.cachedJSON != nil {\n\t\treturn r.cachedJSON, nil\n\t}\n\tif err := r.fetchBody(); err != nil {\n\t\treturn nil, fmt.Errorf(\"getting response body: %w\", err)\n\t}\n\n\tr.bodyMu.RLock()\n\tdefer r.bodyMu.RUnlock()\n\n\tvar v any\n\tif err := json.Unmarshal(r.body, &v); err != nil {\n\t\treturn nil, fmt.Errorf(\"unmarshalling response body to JSON: %w\", err)\n\t}\n\tr.cachedJSON = v\n\n\treturn v, nil\n}\n\n// Ok returns true if status code of response if considered ok, otherwise returns false.\nfunc (r *Response) Ok() bool {\n\tif r.status == 0 || (r.status >= 200 && r.status <= 299) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Request returns the request that led to this response.\nfunc (r *Response) Request() *Request {\n\treturn r.request\n}","sourceCodeStart":752,"sourceCodeEnd":788,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/browser/common/http.go#L752-L788","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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()"],"exampleFix":"// before\nconst res = await page.goto('https://api.example.com/me');\nconst me = await res.json(); // throws on HTML login page\n\n// after\nconst res = await page.goto('https://api.example.com/me');\nconst ct = (res.headers()['content-type'] || '');\nif (!ct.includes('application/json')) {\n  throw new Error('expected JSON, got: ' + ct);\n}\nconst me = await res.json();","handlingStrategy":"validation","validationCode":"const ct = res.headers()['content-type'] || '';\nif (!ct.includes('application/json')) {\n  throw new Error(`non-JSON response (${ct}): ${(await res.text()).slice(0, 120)}`);\n}\nconst data = await res.json();","typeGuard":null,"tryCatchPattern":"try { data = await res.json(); }\ncatch (e) { if (!/unmarshalling response body to JSON/.test(String(e.message))) throw e; /* handle non-JSON */ }","preventionTips":["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"],"tags":["browser","json","parsing","http","response-body"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}