toon-format/toon · error · Error

Incomplete event stream: unclosed objects or arrays

Error message

Incomplete event stream: unclosed objects or arrays

What it means

After consuming all events, the builder's container stack still holds entries, meaning some `startObject`/`startArray` events never got matching `endObject`/`endArray` events. The library requires a complete, balanced event stream to produce a JSON value and refuses to return a partially built structure.

Source

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

          if (parent.currentKey === undefined) {
            throw new Error('Primitive event without preceding key in object')
          }
          setOwnProperty(parent.obj, parent.currentKey, event.value)
          parent.currentKey = undefined
        }
        else if (parent.type === 'array') {
          parent.arr.push(event.value)
        }
      }

      break
    }
  }
}

function finalizeState(state: BuildState): JsonValue {
  if (state.stack.length !== 0) {
    throw new Error('Incomplete event stream: unclosed objects or arrays')
  }

  if (state.root === undefined) {
    throw new Error('No root value built from events')
  }

  return state.root
}

// #endregion

View on GitHub (pinned to 604eac266e)

Solutions

  1. Ensure the producer emits matching endObject/endArray events for every start event
  2. Verify the input source is fully consumed (don't truncate the TOON document or stream)
  3. If handling partial data, wrap the builder call in try/catch and treat it as incomplete input
  4. Regenerate the events from the full input rather than hand-splicing event sequences

Example fix

// before: events = [{type:'startObject'},{type:'key',key:'a'},{type:'primitive',value:1}] // no endObject
// after:  events = [{type:'startObject'},{type:'key',key:'a'},{type:'primitive',value:1},{type:'endObject'}]
Defensive patterns

Strategy: validation

Validate before calling

function isBalanced(events) {
  let depth = 0
  for (const e of events) {
    if (e.type === 'startObject' || e.type === 'startArray') depth++
    if (e.type === 'endObject' || e.type === 'endArray') depth--
    if (depth < 0) return false
  }
  return depth === 0
}

Try / catch

try {
  const value = buildValueFromEvents(events)
} catch (err) {
  if (err instanceof Error && err.message.includes('Incomplete event stream')) {
    // treat as partial data; request/re-read the full source
  } else throw err
}

Prevention

When it happens

Trigger: Feeding buildValueFromEvents or buildValueFromEventsAsync an event iterable that ends while objects/arrays are still open — e.g. truncation during streaming, a producer that omits end events on error paths, or breaking out of the source iteration early so end events are lost.

Common situations: Streaming decode from a network that drops the tail of the data; custom event pipelines that swallow end events on exceptions; manually slicing an event array for debugging and feeding the slice to the builder.

Related errors


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