{"record":{"id":"35151d53987a77b0","repo":"toon-format/toon","slug":"unexpected-content-after-nested-field-group","errorCode":null,"errorMessage":"Unexpected content after nested field group","messagePattern":"Unexpected content after nested field group","errorType":"exception","errorClass":"SyntaxError","httpStatus":null,"severity":"error","filePath":"packages/toon/src/decode/parser.ts","lineNumber":242,"sourceCode":"      throw new SyntaxError('Empty field name in field list')\n    }\n\n    const groupStart = findUnquotedChar(trimmedEntry, OPEN_BRACE)\n    if (groupStart === -1) {\n      return { name: parseStringLiteral(trimmedEntry) }\n    }\n\n    const namePart = trimSpaces(trimmedEntry.slice(0, groupStart))\n    if (!namePart) {\n      throw new SyntaxError('Missing field name before nested field group')\n    }\n\n    const groupEnd = findMatchingBrace(trimmedEntry, groupStart)\n    if (groupEnd === -1) {\n      throw new SyntaxError('Unmatched brace in field list')\n    }\n    if (groupEnd !== trimmedEntry.length - 1) {\n      throw new SyntaxError('Unexpected content after nested field group')\n    }\n\n    const children = parseFieldEntries(trimmedEntry.slice(groupStart + 1, groupEnd), delimiter)\n    return { name: parseStringLiteral(namePart), children }\n  })\n}\n\n/**\n * Splits a field list on the active delimiter at brace depth zero,\n * respecting quoted names and escape sequences.\n */\nfunction splitFieldEntries(content: string, delimiter: Delimiter): string[] {\n  const entries: string[] = []\n  let entryBuffer = ''\n  let inQuotes = false\n  let braceDepth = 0\n  let i = 0\n","sourceCodeStart":224,"sourceCodeEnd":260,"githubUrl":"https://github.com/toon-format/toon/blob/604eac266e35166bed6f5b9e3bd586e9c60a9330/packages/toon/src/decode/parser.ts#L224-L260","documentation":"A nested field group in a field-list entry was properly closed, but the closing `}` was not the last character of the trimmed entry — extra content followed it (e.g. `user{name}extra`). TOON requires the group to terminate the entry, so trailing content is rejected to avoid silently misparsing.","triggerScenarios":"Decoding a field list where text follows a nested group's closing brace, like `items[2]{user{name}x,age}:` — parseFieldEntries checks groupEnd !== trimmedEntry.length - 1 and throws. Reached via parseArrayHeaderLine.","commonSituations":"Hand-edited headers appending delimiters or text after a nested group instead of before it; generators that concatenate fields in the wrong order (group first, then siblings inside the same entry instead of after the comma).","solutions":["Move any sibling fields after the group into a separate delimiter-separated entry, e.g. `{user{name},age}` not `{user{name}age}`","Remove stray characters after the closing `}`","Fix the generator so nested groups are always the last part of their entry","Compare against output from toon.encode for correct field-list ordering"],"exampleFix":"// before\nusers[2]{user{name}age}:\n// after\nusers[2]{user{name},age}:","handlingStrategy":"try-catch","validationCode":"function noContentAfterGroups(toonText) {\n  const m = toonText.match(/\\[\\d+\\]\\{(.*)\\}\\s*:$/m)\n  if (!m) return true\n  return !/\\}[^,;}]/.test(m[1])\n}","typeGuard":null,"tryCatchPattern":"try {\n  const value = toon.decode(text)\n} catch (err) {\n  if (err instanceof SyntaxError && err.message.includes('after nested field group')) {\n    // move trailing text into its own delimiter-separated entry\n  } else throw err\n}","preventionTips":["A nested group must end its entry; put siblings after a delimiter, never after the }","Generate headers with toon.encode rather than string templates","Diff hand-edited headers against encoder output"],"tags":["syntax","toon","nested-fields","parser"],"backgroundTag":"unbalanced-braces","analyzedSha":"604eac266e35166bed6f5b9e3bd586e9c60a9330","analyzedAt":"2026-08-31T11:09:25.043Z","schemaVersion":2},"datasetVersion":"2026-09-01T08:17:40.651Z"}