toon-format/toon · error · ToonDecodeError

Unexpected content after the document root

Error message

Unexpected content after the document root

What it means

In strict mode, decodeDocument requires the entire input to be consumed by the root value; any non-empty line remaining after the root has been decoded triggers this error. This guards against concatenated documents or trailing garbage that would otherwise be silently ignored.

Source

Thrown at packages/toon/src/decode/decoders.ts:173

    { line: line.lineNumber, source: line.raw },
  )
}

function keylessFieldsHeaderError(line: ParsedLine): ToonDecodeError {
  return new ToonDecodeError(
    'Keyless header with a field list is only valid at the document root',
    { line: line.lineNumber, source: line.raw },
  )
}

// Strict decoding never silently discards input, so a line after the root form is an error.
function* assertFullyConsumed(reader: LineReader, strict: boolean): LineRule {
  if (!strict) {
    return
  }
  const line = yield* peekLine(reader)
  if (line) {
    throw new ToonDecodeError(
      'Unexpected content after the document root',
      { line: line.lineNumber, source: line.raw },
    )
  }
}

function assertNoDuplicateKey(key: string, line: ParsedLine, seenKeys: Set<string> | undefined): void {
  if (!seenKeys)
    return
  if (seenKeys.has(key)) {
    throw new ToonDecodeError(
      `Duplicate sibling key "${key}"`,
      { line: line.lineNumber, source: line.raw },
    )
  }
  seenKeys.add(key)
}

View on GitHub (pinned to 604eac266e)

Solutions

  1. Remove the extra content after the root
  2. Split the file into separate documents and decode each individually
  3. Decode with strict:false if trailing content should be tolerated
  4. Wrap multiple records under one root array header

Example fix

// before
name: Ada
extra: orphan

// after
name: Ada
Defensive patterns

Strategy: validation

Validate before calling

function isSingleDocument(text: string): boolean {
  const lines = text.split('\n').map(l => l.trim()).filter(Boolean)
  return lines.length > 0 && !lines.slice(1).some(l => !l.startsWith('- ') && /^[^\s[][^:]*:/.test(l) === false ? false : false) // rely on decode strict for trailing check
}
// simpler pre-check: ensure only one top-level key block by decoding strict:false first and checking reader leftovers via strict decode

Try / catch

try {
  return decode(input, { strict: true })
} catch (e) {
  if (e instanceof ToonDecodeError && e.message.includes('after the document root')) {
    return input.split(/\n(?=\S)/).map(part => decode(part)) // split concatenated docs
  }
  throw e
}

Prevention

When it happens

Trigger: Calling decode with strict:true on input that contains a second top-level block after the root key-value/array, or any trailing non-blank lines.

Common situations: Concatenating two TOON documents into one file; log-style appending of extra records; stray trailing line from a bad copy-paste or a diff artifact.

Related errors


AI-assisted analysis of toon-format/toon@604eac266e (2026-08-31). Data as JSON: /api/errors/86c108d8559648a5. Report an issue: GitHub.