{"record":{"id":"822c25d650b9e444","repo":"JuliusBrussee/caveman","slug":"object-key-is-t","errorCode":null,"errorMessage":"object key is %T","messagePattern":"object key is %T","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"engine/compressors/toon_encode.go","lineNumber":141,"sourceCode":"func readJSONTOONValue(dec *json.Decoder) (toonValue, error) {\n\ttok, err := dec.Token()\n\tif err != nil {\n\t\treturn toonValue{}, err\n\t}\n\tswitch t := tok.(type) {\n\tcase json.Delim:\n\t\tswitch t {\n\t\tcase '{':\n\t\t\tvar fields []toonField\n\t\t\tseen := make(map[string]struct{})\n\t\t\tfor dec.More() {\n\t\t\t\tkeyTok, err := dec.Token()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn toonValue{}, err\n\t\t\t\t}\n\t\t\t\tkey, ok := keyTok.(string)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn toonValue{}, fmt.Errorf(\"object key is %T\", keyTok)\n\t\t\t\t}\n\t\t\t\tif _, duplicate := seen[key]; duplicate {\n\t\t\t\t\treturn toonValue{}, fmt.Errorf(\"duplicate object key %q\", key)\n\t\t\t\t}\n\t\t\t\tseen[key] = struct{}{}\n\t\t\t\tval, err := readJSONTOONValue(dec)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn toonValue{}, err\n\t\t\t\t}\n\t\t\t\tfields = append(fields, toonField{name: key, value: val})\n\t\t\t}\n\t\t\tend, err := dec.Token()\n\t\t\tif err != nil {\n\t\t\t\treturn toonValue{}, err\n\t\t\t}\n\t\t\tif d, ok := end.(json.Delim); !ok || d != '}' {\n\t\t\t\treturn toonValue{}, fmt.Errorf(\"object not closed\")\n\t\t\t}","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/engine/compressors/toon_encode.go#L123-L159","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: state drift\nval, _ := readJSONTOONValue(dec) // consumed '}' already\nkeyTok, _ := dec.Token() // now Delim\n// after: check delim before parsing fields\ntok, _ := dec.Token()\nd, ok := tok.(json.Delim); if !ok || d != '{' { return err }","handlingStrategy":"validation","validationCode":"var probe map[string]any\nif err := json.Unmarshal(input, &probe); err != nil { /* not valid JSON — reject before TOON parse */ }","typeGuard":"func tokIsString(tok json.Token) bool { _, ok := tok.(string); return ok }","tryCatchPattern":"v, err := parseJSONTOON(input)\nif err != nil && strings.HasPrefix(err.Error(), \"object key is\") {\n    return fmt.Errorf(\"malformed token stream: %w\", err)\n}","preventionTips":["Use the standard encoding/json Decoder, no custom token wrappers","Keep parser branch token consumption symmetric","Test nested objects/arrays to catch state drift"],"tags":["toon","json","parser","type-mismatch"],"backgroundTag":"type-mismatch","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}