toon-format/toon · error · ToonDecodeError

Unexpected indentation inside keyed tabular object

Error message

Unexpected indentation inside keyed tabular object

What it means

Inside a keyed tabular object (rows under a `[N]{...}:`-style header), entry rows must all sit at the same depth. In strict mode, a line indented deeper than the established entry depth is rejected because it would represent ambiguous nesting under a row. Non-strict mode skips such lines.

Source

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

  const seenEntryKeys = options.strict ? new Set<string>() : undefined
  let entryCount = 0
  let startLine: number | undefined
  let endLine: number | undefined
  let lastEntryLine: ParsedLine = headerLine

  yield { type: 'startObject' }

  // A keyed scope ends only by dedent or end of input, so every line at entry depth
  // carrying an unquoted colon is an entry row.
  while (true) {
    const line = yield* peekLine(reader)
    if (!line || line.depth <= baseDepth) {
      break
    }

    if (line.depth > entryDepth) {
      if (options.strict) {
        throw new ToonDecodeError(
          'Unexpected indentation inside keyed tabular object',
          { line: line.lineNumber, source: line.raw },
        )
      }
      yield* readLine(reader)
      continue
    }

    if (findUnquotedChar(line.content, COLON) === -1) {
      if (options.strict) {
        throw new ToonDecodeError(
          'Expected entry row inside keyed tabular object',
          { line: line.lineNumber, source: line.raw },
        )
      }
      yield* readLine(reader)
      continue
    }

View on GitHub (pinned to 604eac266e)

Solutions

  1. Re-indent the offending line to match the other entry rows
  2. Remove unintended extra nesting under the row
  3. Decode with strict:false to skip the over-indented line
  4. Normalize indentation with a formatter before decoding

Example fix

// before
users[2]{name}:
  name: Ada
    name: Grace

// after
users[2]{name}:
  name: Ada
  name: Grace
Defensive patterns

Strategy: validation

Validate before calling

function tabularRowsUniformDepth(text: string): boolean {
  const lines = text.split('\n').filter(l => l.trim())
  let entryDepth: number | null = null
  let inTable = false
  for (const l of lines) {
    const d = Math.floor(((l.match(/^ */) ?? [''])[0].length) / 2)
    if (/^ *\[\d+\]\{.*\}:/.test(l)) { inTable = true; entryDepth = null; continue }
    if (inTable) {
      if (d <= 0) { inTable = false; continue }
      if (entryDepth === null) entryDepth = d
      else if (d !== entryDepth) return false
    }
  }
  return true
}

Try / catch

try {
  return decode(input, { strict: true })
} catch (e) {
  if (e instanceof ToonDecodeError && e.message.includes('inside keyed tabular object')) {
    return decode(input, { strict: false })
  }
  throw e
}

Prevention

When it happens

Trigger: decodeKeyedObject (invoked from decodeArrayFromHeader) peeks a line whose depth exceeds the entry depth while options.strict is true — e.g. one row accidentally indented one level further than its siblings.

Common situations: Inconsistent indent after manual editing; mixed tab/space handling making one row appear deeper; copy-paste of a nested example into the middle of a tabular block.

Related errors


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