toon-format/toon · error · ToonDecodeError

Expected entry row inside keyed tabular object

Error message

Expected entry row inside keyed tabular object

What it means

Every line inside a keyed tabular object must be an entry row of the form `key: value`; a line without an unquoted colon cannot be a row. In strict mode decodeKeyedObject throws instead of skipping the malformed line (non-strict reads and discards it).

Source

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

    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
    }

    yield* readLine(reader)
    if (startLine === undefined) {
      startLine = line.lineNumber
    }
    endLine = line.lineNumber
    lastEntryLine = line

    const { key, end } = withLine(line, () => parseKeyToken(line.content, 0))
    assertNoDuplicateKey(key, line, seenEntryKeys)
    yield { type: 'key', key }

View on GitHub (pinned to 604eac266e)

Solutions

  1. Add the missing `key: ` to the offending row
  2. Join the split line back to its key on the previous line
  3. Remove the stray non-row line
  4. Decode with strict:false so the line is skipped

Example fix

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

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

Strategy: validation

Validate before calling

function tabularRowsAllKeyed(text: string): boolean {
  const lines = text.split('\n')
  const tableIdx = lines.findIndex(l => /^ *\[\d+\]\{.*\}:/.test(l))
  if (tableIdx === -1) return true
  for (let i = tableIdx + 1; i < lines.length; i++) {
    const l = lines[i]
    if (!l.trim()) break
    if (!/^ *[^:]+:/.test(l)) return false
  }
  return true
}

Try / catch

try {
  return decode(input, { strict: true })
} catch (e) {
  if (e instanceof ToonDecodeError && e.message.includes('entry row')) {
    return decode(input, { strict: false }) // malformed rows skipped
  }
  throw e
}

Prevention

When it happens

Trigger: decodeKeyedObject, while consuming rows under a keyed tabular array header, finds a line at the entry depth whose content contains no unquoted `:` and options.strict is true.

Common situations: A row missing its key/value separator; prose or comment text accidentally inside the table; a value split across lines leaving a colon-less continuation line.

Related errors


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