{"record":{"id":"7546fe6020cf0733","repo":"jackwener/OpenCLI","slug":"label-returned-a-malformed-numeric-field","errorCode":null,"errorMessage":"${label} returned a malformed numeric field","messagePattern":"(.+?) returned a malformed numeric field","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/juejin/utils.js","lineNumber":153,"sourceCode":"    if (payload.data.length === 0) {\n        throw new EmptyResultError(label, `${label} returned no articles.`);\n    }\n    return payload.data;\n}\n\nfunction readArticleId(value, label) {\n    const id = String(value ?? '').trim();\n    if (!JUEJIN_ID.test(id)) {\n        throw new CommandExecutionError(`${label} returned a malformed article id`);\n    }\n    return id;\n}\n\nfunction readOptionalNumber(value, label) {\n    if (value == null) return null;\n    const n = Number(value);\n    if (!Number.isFinite(n)) {\n        throw new CommandExecutionError(`${label} returned a malformed numeric field`);\n    }\n    return n;\n}\n\n/** Map a recommend-feed row (`item_info.article_info` / `author_user_info`) to a flat shape. */\nexport function mapFeedItem(row, rank) {\n    const info = row?.item_info ?? {};\n    const article = info.article_info ?? {};\n    const author = info.author_user_info ?? {};\n    const tags = Array.isArray(info.tags)\n        ? info.tags.map(t => t?.tag_name).filter(Boolean).slice(0, 6).join(', ')\n        : '';\n    const articleId = readArticleId(article.article_id, 'juejin recommend');\n    return {\n        rank,\n        article_id: articleId,\n        title: String(article.title ?? '').trim(),\n        brief: String(article.brief_content ?? '').trim(),","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/juejin/utils.js#L135-L171","documentation":"readOptionalNumber validates optional numeric fields coming from Juejin API rows (feed items, hot items). If a field is present (not null/undefined) but Number() yields NaN/Infinity — e.g. a string like 'abc' or an object — it throws CommandExecutionError because the API returned data that does not fit the expected numeric schema. This guards downstream mapping code from silently producing NaN values.","triggerScenarios":"mapFeedItem or mapHotItem passes an `article_info`/`author_user_info` field (e.g. view_count, digg_count) that is a non-numeric string or object instead of a number or numeric string; Juejin changed its API response shape; the field is a placeholder like '-' or ''.","commonSituations":"Juejin ships an API schema change (field becomes a formatted string like '1.2w'); a partially-degraded/edge response row has nulls coerced oddly; scraping a preview/test endpoint with mock data.","solutions":["Check the raw API row (curl https://api.juejin.cn/... or log `value`) to see the actual field value","Re-run the command — the malformed value is often transient garbage from one row","Update the adapter's mapFeedItem/mapHotItem if Juejin changed the field format, adding normalization (e.g. parse '1.2w')","If a specific field is chronically non-numeric, pass null/omit it instead of feeding it to readOptionalNumber"],"exampleFix":"// before\nreadOptionalNumber(row.item_info.article_info.view_count, 'view_count') // value = '1.2万' -> throws\n// after\nfunction parseCNNumber(v) {\n  if (v == null) return null;\n  if (typeof v === 'string') {\n    const m = v.match(/^([\\d.]+)([万w]?)$/i);\n    if (m) return Number(m[1]) * (m[2] ? 10000 : 1);\n  }\n  return readOptionalNumber(v, 'view_count');\n}","handlingStrategy":"validation","validationCode":"function safeNumber(v) {\n  if (v == null) return null;\n  const n = Number(v);\n  return Number.isFinite(n) ? n : null;\n}\nconst views = safeNumber(row?.item_info?.article_info?.view_count);\nif (views === null && row?.item_info?.article_info?.view_count != null) {\n  console.warn('skipping malformed row', row.item_info.article_id);\n}","typeGuard":"function isNumericField(v) {\n  return v == null || (typeof v === 'number' && Number.isFinite(v)) ||\n         (typeof v === 'string' && v.trim() !== '' && Number.isFinite(Number(v)));\n}","tryCatchPattern":"try {\n  const item = mapFeedItem(row, rank);\n} catch (e) {\n  if (/malformed numeric field/.test(e.message)) {\n    console.warn(`skip row ${row?.item_info?.article_id}: ${e.message}`);\n    return null;\n  }\n  throw e;\n}","preventionTips":["Pre-validate each API row with a small normalizer before mapping","Log raw payloads when Juejin changes its API schema","Skip-and-log malformed rows instead of failing the whole feed","Pin/alert on Juejin API shape changes via a smoke test"],"tags":["api","data-validation","juejin"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}