{"record":{"id":"b9dec4ae0b314427","repo":"jackwener/OpenCLI","slug":"eastmoney-convertible-returned-malformed-row-at-ra","errorCode":null,"errorMessage":"eastmoney convertible returned malformed row at rank ${rank}","messagePattern":"eastmoney convertible returned malformed row at rank (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/eastmoney/convertible.js","lineNumber":85,"sourceCode":"  if (!Array.isArray(diff)) {\n    throw new CommandExecutionError('eastmoney convertible returned malformed diff data');\n  }\n  if (diff.length === 0) {\n    throw new EmptyResultError('eastmoney convertible');\n  }\n  return diff;\n}\n\n// Map a raw eastmoney clist `diff` item to an output row.\n//\n// #2109: f238 / f239 were previously emitted as `remainingYears` / `ytm`, but\n// cross-verification (12/12 fingerprint hits) shows f239 is the putback trigger\n// price (= convPrice × 0.7) and f238 is the pure-bond premium %. Real YTM /\n// remaining term are not in this response's `fields`; adding the correct f-codes\n// is a follow-up that needs a live push2 field dump cross-checked against jisilu.\nexport function mapConvertibleRow(it, rank) {\n  if (!it || typeof it !== 'object' || Array.isArray(it)) {\n    throw new CommandExecutionError(`eastmoney convertible returned malformed row at rank ${rank}`);\n  }\n  const bondCode = normalizeEastmoneyIdentityString(it.f12, 'f12', '');\n  const bondName = normalizeEastmoneyIdentityString(it.f14, 'f14', bondCode);\n  const stockCode = normalizeEastmoneyIdentityString(it.f232, 'f232', bondCode);\n  const stockName = normalizeEastmoneyIdentityString(it.f234, 'f234', bondCode);\n  for (const field of NUMERIC_FIELDS) {\n    normalizeEastmoneyNumeric(it[field], field, bondCode);\n  }\n  return {\n    rank,\n    bondCode,\n    bondName,\n    bondPrice: normalizeEastmoneyNumeric(it.f2, 'f2', bondCode),\n    bondChangePct: normalizeEastmoneyNumeric(it.f3, 'f3', bondCode),\n    stockCode,\n    stockName,\n    stockPrice: normalizeEastmoneyNumeric(it.f229, 'f229', bondCode),\n    stockChangePct: normalizeEastmoneyNumeric(it.f230, 'f230', bondCode),","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/eastmoney/convertible.js#L67-L103","documentation":"Thrown by mapConvertibleRow (clis/eastmoney/convertible.js:85) when a single `diff` element is not a plain object (null, primitive, or array). Each diff item is expected to be an object keyed by eastmoney field codes (f12, f14, f232, ...). The error includes the 1-based rank so the offending row can be located. It is a CommandExecutionError from the row-mapping layer, meaning the envelope passed but a row inside is corrupt.","triggerScenarios":"mapConvertibleRows slices the diff array and maps each item; any element that is null or non-object triggers this with `rank = index + 1`. Happens when eastmoney pads diff with nulls, when fixtures are malformed, or when a JSON transform converts rows unexpectedly.","commonSituations":"Eastmoney returning sparse/null placeholder rows for suspended bonds; test fixtures containing nulls; response post-processing (e.g. CSV-ish serializers) flattening rows; calling mapConvertibleRow directly with wrong arguments in unit tests.","solutions":["Filter non-object entries before mapping: `diff.filter(it => it && typeof it === 'object' && !Array.isArray(it))`.","Inspect the raw response at the reported rank to see what eastmoney actually returned.","Update test fixtures to contain only plain-object rows.","Re-fetch — a transiently corrupt payload usually differs on retry.","If eastmoney systematically emits null rows, add a skip-with-warning instead of throwing."],"exampleFix":"// before\nreturn capped.map((it, i) => mapConvertibleRow(it, i + 1));\n// after\nreturn capped\n  .filter(it => it && typeof it === 'object' && !Array.isArray(it))\n  .map((it, i) => mapConvertibleRow(it, i + 1));","handlingStrategy":"validation","validationCode":"const cleanRows = diff.filter(it => it && typeof it === 'object' && !Array.isArray(it));\nconst rows = mapConvertibleRows(cleanRows, limit);","typeGuard":"function isConvertibleRow(it) {\n  return it !== null && typeof it === 'object' && !Array.isArray(it) &&\n    typeof it.f12 === 'string' && it.f12.trim() !== '';\n}","tryCatchPattern":"try {\n  return mapConvertibleRows(diff, limit);\n} catch (e) {\n  const m = String(e.message).match(/malformed row at rank (\\d+)/);\n  if (m) {\n    console.warn(`skipping corrupt row at rank ${m[1]}`);\n    return mapConvertibleRows(diff.filter(isConvertibleRow), limit);\n  }\n  throw e;\n}","preventionTips":["Filter diff rows for plain objects before mapping.","Keep null-tolerant handling for suspended/delisted bonds eastmoney pads with nulls.","Validate fixtures row-by-row in tests.","Log the offending rank's raw item when the error fires."],"tags":["api","data-mapping","eastmoney","validation"],"backgroundTag":"malformed-api-response-envelope","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}