toon-format/toon · error · Error

Primitive event without preceding key in object

Error message

Primitive event without preceding key in object

What it means

The event-builder received a `primitive` event while the current container is an object whose `currentKey` is undefined. In TOON's stream-event contract every value inside an object must be preceded by a `key` event, so a primitive with no pending key is a malformed event stream and the builder aborts instead of silently dropping the value. This guards against producers that emit events out of order.

Source

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

      const parent = stack[stack.length - 1]!
      if (parent.type !== 'object') {
        throw new Error('Key event outside of object context')
      }

      parent.currentKey = event.key

      break
    }

    case 'primitive': {
      if (stack.length === 0) {
        state.root = event.value
      }
      else {
        const parent = stack[stack.length - 1]!
        if (parent.type === 'object') {
          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')
  }

View on GitHub (pinned to 604eac266e)

Solutions

  1. Fix the event producer so every primitive inside an object is preceded by a `key` event
  2. If you control the stream, log the events immediately before the failure to find the missing key
  3. Use the official toon encode/decode APIs instead of hand-assembling events
  4. If filtering/transforming event streams, ensure transforms preserve key/value pairing

Example fix

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

Strategy: validation

Validate before calling

function isValidEventStream(events) {
  let inObject = false, pendingKey = false
  for (const e of events) {
    if (e.type === 'startObject') { inObject = true; pendingKey = false }
    else if (e.type === 'endObject') { inObject = false; pendingKey = false }
    else if (e.type === 'key') { if (!inObject) return false; pendingKey = true }
    else if (e.type === 'primitive') { if (inObject && !pendingKey) return false; pendingKey = false }
  }
  return true
}

Try / catch

try {
  const value = buildValueFromEvents(events)
} catch (err) {
  if (err instanceof Error && err.message.includes('without preceding key')) {
    // regenerate events or reject malformed stream
  } else throw err
}

Prevention

When it happens

Trigger: Calling buildValueFromEvents (or the async variant) with an event iterable where a `primitive` event appears inside an object without a preceding `key` event — e.g. hand-written event generators, custom tokenizers, or a buggy custom encoder that skips key events.

Common situations: Developers writing custom TOON encoders or streaming pipelines that synthesize JsonStreamEvent sequences themselves; tools post-processing or filtering event streams and accidentally removing key events; version mismatches where an older producer emits a different event grammar than the decoder expects.

Related errors


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