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
- Fix the event producer so every primitive inside an object is preceded by a `key` event
- If you control the stream, log the events immediately before the failure to find the missing key
- Use the official toon encode/decode APIs instead of hand-assembling events
- 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
- Always emit a key event before each value inside an object
- Use toon's official encoder to produce events rather than hand-building them
- Unit-test custom event producers against round-trip encode/decode
- Validate event streams with a small checker before building
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
- Incomplete event stream: unclosed objects or arrays
- No root value built from events
- Top-level document must start with a key-value or array-head
- Indentation depth jump: expected depth ${parentDepth + 1}, b
- Unexpected bare token line outside root primitive position
AI-assisted analysis of toon-format/toon@604eac266e (2026-08-31).
Data as JSON: /api/errors/93a132c0ceaa0024.
Report an issue: GitHub.