{"record":{"id":"b2b9998828b7e593","repo":"badges/shields","slug":"unparseable-json-response","errorCode":null,"errorMessage":"unparseable json response","messagePattern":"unparseable json response","errorType":"exception","errorClass":"InvalidResponse","httpStatus":null,"severity":"error","filePath":"core/base-service/json.js","lineNumber":20,"sourceCode":"import emojic from 'emojic'\nimport { InvalidResponse } from './errors.js'\nimport trace from './trace.js'\n\n/**\n * Parse a JSON response buffer. Throws an `InvalidResponse` error when the\n * JSON is unparseable.\n *\n * @param {string|Buffer} buffer - The raw response body.\n * @returns {object|Array} The parsed JSON value.\n */\nfunction parseJson(buffer) {\n  const logTrace = (...args) => trace.logTrace('fetch', ...args)\n  let json\n  try {\n    json = JSON.parse(buffer)\n  } catch (err) {\n    logTrace(emojic.dart, 'Response JSON (unparseable)', buffer)\n    throw new InvalidResponse({\n      prettyMessage: 'unparseable json response',\n      underlyingError: err,\n    })\n  }\n  logTrace(emojic.dart, 'Response JSON (before validation)', json, {\n    deep: true,\n  })\n  return json\n}\n\nexport { parseJson }\n","sourceCodeStart":2,"sourceCodeEnd":32,"githubUrl":"https://github.com/badges/shields/blob/766fd8bc89a90b8534dc573ab72dec30215ab1ec/core/base-service/json.js#L2-L32","documentation":"parseJson parses an upstream response buffer with JSON.parse. When the body is not valid JSON, Shields throws InvalidResponse with prettyMessage 'unparseable json response'. This distinguishes 'provider responded but sent garbage/HTML' from 'provider unreachable', typically an error page, login HTML, or truncated body.","triggerScenarios":"JSON.parse(buffer) throws inside parseJson — upstream returned HTML (login/error page), empty body, gzip/charset mangling, or a JSONL/truncated response where a JSON schema was expected.","commonSituations":"Provider rate-limits with an HTML error page, auth token expired causing a redirect to a login page, CDN/proxy injecting interstitials, upstream API version change altering content-type.","solutions":["Log/inspect the raw response body (Shields traces it via logTrace as 'Response JSON (unparseable)').","Verify API credentials — an expired token often yields HTML error pages.","Check the upstream URL and any API version in the path.","Confirm the endpoint actually returns application/json (curl with -i).","Add caching/rate limiting if the provider is throttling you into error pages."],"exampleFix":"// before\nconst res = await fetch(url) // returns HTML login page\nconst json = await res.json() // InvalidResponse: unparseable json response\n// after\nconst text = await res.text()\nif (!text.trim().startsWith('{') && !text.trim().startsWith('[')) throw new Error('non-JSON response: ' + text.slice(0, 100))\nconst json = JSON.parse(text)","handlingStrategy":"validation","validationCode":"const looksLikeJson = text => { const t = text.trimStart(); return t.startsWith('{') || t.startsWith('[') }\nif (!looksLikeJson(buffer)) throw new Error('upstream returned non-JSON body')","typeGuard":"function isJsonObject(v) { return typeof v === 'object' && v !== null && !Array.isArray(v) }","tryCatchPattern":"try {\n  const json = JSON.parse(buffer)\n} catch (err) {\n  console.error('non-JSON body (first 200 chars):', String(buffer).slice(0, 200))\n  return fallbackBadge\n}","preventionTips":["Always check response Content-Type before parsing.","Log the raw body on parse failure to spot HTML error/login pages.","Keep API tokens valid — expired auth often yields HTML.","Watch upstream provider changelogs for content-type changes."],"tags":["json","parsing","upstream","api"],"backgroundTag":"unparseable-json-response","analyzedSha":"766fd8bc89a90b8534dc573ab72dec30215ab1ec","analyzedAt":"2026-08-30T01:40:27.499Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}