JuliusBrussee/caveman · error
object key is %T
Error message
object key is %T
What it means
During JSON-to-TOON parsing, readJSONTOONValue expects object keys to be JSON strings. The encoding/json decoder normally guarantees this, so "object key is %T" fires only if a non-string token (e.g. json.Delim or number) appears where a key is expected — meaning the token stream is malformed or the parser's state machine drifted. The error includes the Go type of the offending token for debugging.
Solutions
- Use the standard encoding/json Decoder directly — it only emits strings as object keys; remove custom decoder wrappers.
- Check call sites of readJSONTOONValue for off-by-one token consumption (reading a value position as a key).
- Add a regression test with nested objects/arrays to lock the token-stream state machine.
Example fix
// before: state drift
val, _ := readJSONTOONValue(dec) // consumed '}' already
keyTok, _ := dec.Token() // now Delim
// after: check delim before parsing fields
tok, _ := dec.Token()
d, ok := tok.(json.Delim); if !ok || d != '{' { return err } Defensive patterns
Strategy: validation
Validate before calling
var probe map[string]any
if err := json.Unmarshal(input, &probe); err != nil { /* not valid JSON — reject before TOON parse */ } Type guard
func tokIsString(tok json.Token) bool { _, ok := tok.(string); return ok } Try / catch
v, err := parseJSONTOON(input)
if err != nil && strings.HasPrefix(err.Error(), "object key is") {
return fmt.Errorf("malformed token stream: %w", err)
} Prevention
- Use the standard encoding/json Decoder, no custom token wrappers
- Keep parser branch token consumption symmetric
- Test nested objects/arrays to catch state drift
When it happens
Trigger: In readJSONTOONValue's object branch, dec.Token() yields a token whose type assertion keyTok.(string) fails while reading an object's key position — reached from parseJSONTOON or nested recursion.
Common situations: A bug in a custom json.Token stream or a wrapper decoder around the standard one; parser refactors that call readJSONTOONValue after already consuming the closing delimiter; corrupted/injected token streams in tests.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- array not closed
- unexpected delimiter
- unsupported token %T
- duplicate object key
- not losslessly TOON-encodable (invalid JSON, or…
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/822c25d650b9e444.
Report an issue: GitHub.
Appendix: source
Thrown at engine/compressors/toon_encode.go:141
func readJSONTOONValue(dec *json.Decoder) (toonValue, error) {
tok, err := dec.Token()
if err != nil {
return toonValue{}, err
}
switch t := tok.(type) {
case json.Delim:
switch t {
case '{':
var fields []toonField
seen := make(map[string]struct{})
for dec.More() {
keyTok, err := dec.Token()
if err != nil {
return toonValue{}, err
}
key, ok := keyTok.(string)
if !ok {
return toonValue{}, fmt.Errorf("object key is %T", keyTok)
}
if _, duplicate := seen[key]; duplicate {
return toonValue{}, fmt.Errorf("duplicate object key %q", key)
}
seen[key] = struct{}{}
val, err := readJSONTOONValue(dec)
if err != nil {
return toonValue{}, err
}
fields = append(fields, toonField{name: key, value: val})
}
end, err := dec.Token()
if err != nil {
return toonValue{}, err
}
if d, ok := end.(json.Delim); !ok || d != '}' {
return toonValue{}, fmt.Errorf("object not closed")
}View on GitHub (pinned to 3ee70a1026)