toon-format/toon · error · Error

Object startObject event without preceding key

Error message

Object startObject event without preceding key

What it means

applyEvent received a startObject event while the current parent is an object with no pending currentKey. Object values must be assigned under a key, so the builder throws to reject a malformed event stream.

Source

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

// #endregion

// #region Shared event handlers

function applyEvent(state: BuildState, event: JsonStreamEvent): void {
  const { stack } = state

  switch (event.type) {
    case 'startObject': {
      const obj: JsonObject = {}

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

          setOwnProperty(parent.obj, parent.currentKey, obj)
          parent.currentKey = undefined
        }
        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')

View on GitHub (pinned to 604eac266e)

Solutions

  1. Emit a key event before startObject when nesting inside an object
  2. Fix the upstream event producer's key-then-value ordering
  3. Validate the event stream before feeding it to buildValueFromEvents

Example fix

// before
const events = [{type:'startObject'},{type:'endObject'}]
// after
const events = [{type:'key',key:'a'},{type:'startObject'},{type:'endObject'}]
Defensive patterns

Strategy: validation

Validate before calling

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

Try / catch

try {
  const value = buildValueFromEvents(events)
} catch (e) {
  if (e.message.includes('without preceding key')) {
    throw new Error('Event stream violates key-before-value ordering: ' + e.message)
  }
  throw e
}

Prevention

When it happens

Trigger: buildValueFromEvents/buildValueFromEventsAsync given a stream where startObject appears inside an object context without an intervening key event.

Common situations: Bugs in custom event emitters/tokenizers; hand-constructed event arrays in tests missing key events.

Related errors


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