toon-format/toon · error · Error

Array startArray event without preceding key

Error message

Array startArray event without preceding key

What it means

applyEvent received a startArray event while the parent is an object with no pending currentKey. Arrays inside objects must be assigned under a key, so the builder throws.

Source

Thrown at packages/toon/src/decode/event-builder.ts:104

      if (stack.length === 0) {
        state.root = context.obj
      }

      break
    }

    case 'startArray': {
      const arr: JsonValue[] = []

      if (stack.length === 0) {
        stack.push({ type: 'array', arr })
      }
      else {
        const parent = stack[stack.length - 1]!
        if (parent.type === 'object') {
          if (parent.currentKey === undefined) {
            throw new Error('Array startArray event without preceding key')
          }
          setOwnProperty(parent.obj, parent.currentKey, arr)
          parent.currentKey = undefined
        }
        else if (parent.type === 'array') {
          parent.arr.push(arr)
        }

        stack.push({ type: 'array', arr })
      }

      break
    }

    case 'endArray': {
      if (stack.length === 0) {
        throw new Error('Unexpected endArray event')
      }

View on GitHub (pinned to 604eac266e)

Solutions

  1. Emit a key event before startArray when inside an object
  2. Fix the upstream tokenizer/emitter ordering
  3. Validate key-before-value ordering in the stream beforehand

Example fix

// before
[{type:'startObject'},{type:'startArray'},{type:'endArray'}]
// after
[{type:'startObject'},{type:'key',key:'items'},{type:'startArray'},{type:'endArray'}]
Defensive patterns

Strategy: validation

Validate before calling

function keysPrecedeArrays(events) {
  let inObject = false, expectKey = false
  for (const ev of events) {
    if (ev.type === 'startObject') { inObject = true; expectKey = false }
    else if (ev.type === 'endObject') inObject = false
    else if (ev.type === 'key') expectKey = true
    else if (ev.type === 'startArray' && inObject && !expectKey) return false
    else if (ev.type === 'endArray') expectKey = false
  }
  return true
}

Try / catch

try {
  const value = buildValueFromEvents(events)
} catch (e) {
  if (e.message.includes('Array startArray event without preceding key')) {
    throw new Error('Nested array emitted without a key in object context')
  }
  throw e
}

Prevention

When it happens

Trigger: A stream where startArray appears inside an object context without a preceding key event.

Common situations: Buggy event producers; manually written event sequences omitting keys before nested arrays.

Related errors


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