toon-format/toon · error · Error

Mismatched endObject event

Error message

Mismatched endObject event

What it means

applyEvent popped a context on endObject but the context was not an object (e.g. an array was open). Start and end event kinds must match.

Source

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

        }
        else if (parent.type === 'array') {
          parent.arr.push(obj)
        }

        stack.push({ type: 'object', obj })
      }

      break
    }

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

      const context = stack.pop()!
      if (context.type !== 'object') {
        throw new Error('Mismatched endObject event')
      }

      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') {

View on GitHub (pinned to 604eac266e)

Solutions

  1. Emit endArray to close startArray and endObject for startObject
  2. Fix nesting-state tracking in the event producer
  3. Pre-validate matched start/end kinds

Example fix

// before
[{type:'startArray'},{type:'endObject'}] // mismatched
// after
[{type:'startArray'},{type:'endArray'}]
Defensive patterns

Strategy: validation

Validate before calling

function endKindsMatch(events) {
  const stack = []
  for (const ev of events) {
    if (ev.type === 'startObject') stack.push('object')
    else if (ev.type === 'startArray') stack.push('array')
    else if (ev.type === 'endObject') { if (stack.pop() !== 'object') return false }
    else if (ev.type === 'endArray') { if (stack.pop() !== 'array') return false }
  }
  return stack.length === 0
}

Try / catch

try {
  const value = buildValueFromEvents(events)
} catch (e) {
  if (e.message === 'Mismatched endObject event') {
    throw new Error('Crossed nesting: endObject closed a non-object context')
  }
  throw e
}

Prevention

When it happens

Trigger: A stream where startArray is closed by endObject (crossed nesting) passed to buildValueFromEvents/Async.

Common situations: Custom emitters mixing up begin/end event names; streaming parsers that mis-track nesting state.

Related errors


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