{"record":{"id":"3b58e0ddfaa709df","repo":"toon-format/toon","slug":"expected-expectedcount-list-form-items-but-fou","errorCode":null,"errorMessage":"Expected ${expectedCount} list-form items, but found more","messagePattern":"Expected (.+?) list-form items, but found more","errorType":"exception","errorClass":"ToonDecodeError","httpStatus":null,"severity":"error","filePath":"packages/toon/src/decode/validation.ts","lineNumber":29,"sourceCode":"  itemType: string,\n  options: { strict: boolean },\n  line: ParsedLine,\n): void {\n  if (options.strict && actual !== expected) {\n    throw new ToonDecodeError(\n      `Expected ${expected} ${itemType}, but got ${actual}`,\n      { line: line.lineNumber, source: line.raw },\n    )\n  }\n}\n\nexport function validateNoExtraListItems(\n  nextLine: ParsedLine | undefined,\n  itemDepth: Depth,\n  expectedCount: number,\n): void {\n  if (nextLine?.depth === itemDepth && nextLine.content.startsWith(LIST_ITEM_PREFIX)) {\n    throw new ToonDecodeError(\n      `Expected ${expectedCount} list-form items, but found more`,\n      { line: nextLine.lineNumber, source: nextLine.raw },\n    )\n  }\n}\n\nexport function validateNoExtraTabularRows(\n  nextLine: ParsedLine | undefined,\n  rowDepth: Depth,\n  header: ArrayHeaderInfo,\n): void {\n  if (\n    nextLine?.depth === rowDepth\n    && !nextLine.content.startsWith(LIST_ITEM_PREFIX)\n    && isDataRow(nextLine.content, header.delimiter)\n  ) {\n    throw new ToonDecodeError(\n      `Expected ${header.length} tabular rows, but found more`,","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/toon-format/toon/blob/604eac266e35166bed6f5b9e3bd586e9c60a9330/packages/toon/src/decode/validation.ts#L11-L47","documentation":"Thrown by validateNoExtraListItems when a list-form array declared with [N] items contains MORE list items than declared: after reading N items, the next line is still a list-item bullet ('- ') at the same depth. This is a strict/structural integrity check that prevents silently truncating extra data. Unlike error 35 it is not gated on strict mode in this function — an extra item at item depth always throws.","triggerScenarios":"Decoding a list-form array (decodeListArray) whose body contains more '- ' bullet lines at the array's item depth than the header count [N] declares.","commonSituations":"Hand-edited or merged TOON files where bullets were appended without updating the count; a client serializing N+1 items but a stale header; copy/paste duplication of a bullet line.","solutions":["Delete the extra bullet lines beyond the N declared items","Update the header count from [N] to the actual number of '- ' items","If extra items are plausible input, validate/normalize the payload before decode"],"exampleFix":"// before\nitems[2]:\n  - a\n  - b\n  - c\n// after\nitems[3]:\n  - a\n  - b\n  - c","handlingStrategy":"validation","validationCode":"const bullets = lines.filter(l => /^\\s*- /.test(l)).length\nif (bullets !== declaredCount) throw new Error(`list has ${bullets} items, header declares ${declaredCount}`)","typeGuard":"function listItemsMatchCount(body: string[], n: number): boolean { return body.filter(l => l.trimStart().startsWith('- ')).length === n }","tryCatchPattern":"try { decode(input) } catch (e) { if (e instanceof ToonDecodeError && e.message.includes('list-form items, but found more')) console.error('extra list item near line', e.line?.lineNumber, e.line?.source) }","preventionTips":["Recompute the header count after any merge or edit of list items","Deduplicate pasted bullet blocks","Pre-flight decode payloads from untrusted sources"],"tags":["toon","decode","validation","structure"],"backgroundTag":"count-mismatch","analyzedSha":"604eac266e35166bed6f5b9e3bd586e9c60a9330","analyzedAt":"2026-08-31T11:09:25.043Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}