{"record":{"id":"e3a155ba287c8e96","repo":"jackwener/OpenCLI","slug":"label-returned-malformed-json-err-message-e3a155","errorCode":null,"errorMessage":"${label} returned malformed JSON: ${err?.message ?? err}","messagePattern":"(.+?) returned malformed JSON: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/rest-countries/utils.js","lineNumber":77,"sourceCode":"            `${label} request failed: ${err?.message ?? err}`,\n            'Check that restcountries.com is reachable from this network.',\n        );\n    }\n    if (resp.status === 404) {\n        throw new EmptyResultError(label, `REST Countries returned 404 for ${url}.`);\n    }\n    if (resp.status === 429) {\n        throw new CommandExecutionError(`${label} returned HTTP 429 (rate limited)`);\n    }\n    if (!resp.ok) {\n        throw new CommandExecutionError(`${label} returned HTTP ${resp.status}`);\n    }\n    let body;\n    try {\n        body = await resp.json();\n    }\n    catch (err) {\n        throw new CommandExecutionError(`${label} returned malformed JSON: ${err?.message ?? err}`);\n    }\n    return body;\n}\n\n/** Convert REST Countries' `{cur: {name, symbol}}` map to a comma-joined list. */\nexport function joinCurrencies(currencies) {\n    if (!currencies || typeof currencies !== 'object') return '';\n    return Object.entries(currencies)\n        .map(([code, info]) => {\n            const name = info && typeof info.name === 'string' ? info.name : '';\n            return name ? `${code} (${name})` : code;\n        })\n        .join(', ');\n}\n\n/** Convert `{eng: 'English', fra: 'French'}` map to a comma-joined list of language names. */\nexport function joinLanguages(languages) {\n    if (!languages || typeof languages !== 'object') return '';","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/rest-countries/utils.js#L59-L95","documentation":"restCountriesFetch throws CommandExecutionError when resp.json() fails, i.e. the HTTP 200 body is not parseable JSON. The library expects REST Countries to always return JSON on success, so a non-JSON body indicates the response was intercepted or corrupted.","triggerScenarios":"The API returns status 200 but the body is HTML (captive portal, proxy block page, Cloudflare interstitial), a truncated body, or empty content, causing JSON.parse inside resp.json() to reject.","commonSituations":"Corporate proxy or Wi-Fi captive portal replacing the response with an HTML page; aggressive ad-blockers/privacy tools intercepting the request; network interruption truncating the body; REST Countries serving an error page with a 200 status.","solutions":["Retry the request — truncation/corruption is often transient","Check whether a proxy, VPN, or captive portal is rewriting responses (fetch the URL in a browser and inspect the body)","Disable intercepting extensions or exclusion-list restcountries.com","Report a bug if the API persistently returns non-JSON"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"null","typeGuard":"null","tryCatchPattern":"try {\n  await restCountriesCommand(args);\n} catch (e) {\n  if (String(e.message).includes('malformed JSON')) {\n    // likely a proxy/captive portal; surface network context to the user\n    throw new Error('Non-JSON response from restcountries.com — check proxy/VPN/network', { cause: e });\n  }\n  throw e;\n}","preventionTips":["Test restcountries.com in a browser when on corporate networks","Exclude the API domain from proxies/interceptors","Retry once on JSON parse failures (truncation is often transient)","Pin to the documented API version"],"tags":["json","network","response-parsing"],"backgroundTag":"malformed-json-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}