{"record":{"id":"70804bf687ac8653","repo":"redis/go-redis","slug":"redis-resp3-map-key-must-be-a-scalar-type-got-t","errorCode":null,"errorMessage":"redis: RESP3 map key must be a scalar type, got %T","messagePattern":"redis: RESP3 map key must be a scalar type, got %T","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/proto/reader.go","lineNumber":469,"sourceCode":"func (r *Reader) readMap(line []byte) (map[interface{}]interface{}, error) {\n\tn, err := replyLen(line)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tm := make(map[interface{}]interface{}, n)\n\tfor i := 0; i < n; i++ {\n\t\tk, err := r.ReadReply()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// Reject unhashable keys (arrays/maps) before they are used as a map\n\t\t// key, which would otherwise panic. This check must run before the\n\t\t// value is read so it also guards the Nil and RedisError paths below,\n\t\t// which write the key into the map and continue.\n\t\tswitch k.(type) {\n\t\tcase []interface{}, map[interface{}]interface{}:\n\t\t\treturn nil, fmt.Errorf(\"redis: RESP3 map key must be a scalar type, got %T\", k)\n\t\t}\n\n\t\tv, err := r.ReadReply()\n\t\tif err != nil {\n\t\t\tif err == Nil {\n\t\t\t\tm[k] = nil\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err, ok := err.(RedisError); ok {\n\t\t\t\tm[k] = err\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\n\t\tm[k] = v\n\t}\n\treturn m, nil","sourceCodeStart":451,"sourceCodeEnd":487,"githubUrl":"https://github.com/redis/go-redis/blob/c5cad058c72f58370553b48566302303cf8a2e89/internal/proto/reader.go#L451-L487","documentation":"go-redis parses RESP3 map replies into a Go map[interface{}]interface{}, so every key must be hashable (a scalar: string, int, bool, etc.). When the server sends a map whose key is itself an array or a nested map, such a value cannot be used as a Go map key (it would panic), so the library detects []interface{} and map[interface{}]interface{} keys and returns this error instead. This is a deliberate guard: it protects the caller from a runtime panic deep inside map assignment.","triggerScenarios":"Any command whose RESP3 reply is a map type ('%') with a non-scalar key — practically this comes from custom Lua scripts (EVAL/EVALSHA) or Redis functions (FCALL) that return a map keyed by an array/table, or from modules that construct RESP3 maps with array keys. It fires inside ReadReply() while parsing the response of any Cmdable method, most commonly EVAL/EVALSHA/FCALL or generic Do() calls.","commonSituations":"Migrating a Lua script to RESP3 (Protocol: 3): under RESP2 the same script returned a flat array and worked; under RESP3 Redis converts Lua table replies with mixed keys to a RESP3 map, surfacing array keys. Copying a script written for another client library that tolerates non-scalar keys. A module or custom command emitting maps with composite keys.","solutions":["Change the Lua script / module command so it returns a flat array (key and value as consecutive elements) instead of a table/map with composite keys, then read the result as []interface{}.","Force RESP2 on that connection (Protocol: 2) so map replies are delivered as flat arrays and never hit the RESP3 map parser.","If the reply shape is unavoidable, use a raw/custom command that returns the data via a type you control (e.g. have the script JSON-encode the structure and decode it in Go).","Upgrade go-redis: check the release notes — the guard itself was added to convert a panic into this error, so newer versions handle edge shapes differently."],"exampleFix":"-- before: Lua returns a map keyed by an array (RESP3 map with composite key)\nreturn {[{1,2}] = \"v\"}\n-- after: return a flat array instead\nreturn {1, 2, \"v\"}","handlingStrategy":"type-guard","validationCode":"// Pre-validate what the Lua/module command will return: inspect the script's\n// return shape in staging with redis-cli --eval and confirm keys are scalars.","typeGuard":"func isScalarReply(v interface{}) bool {\n    switch v.(type) {\n    case []interface{}, map[interface{}]interface{}:\n        return false\n    }\n    return true\n}","tryCatchPattern":"res, err := client.Do(ctx, \"EVAL\", script, \"1\", key).Result()\nif err != nil {\n    if strings.HasPrefix(err.Error(), \"redis: RESP3 map key must be a scalar type\") {\n        return fmt.Errorf(\"script returns non-scalar map keys; rewrite script to return a flat array: %w\", err)\n    }\n    return err\n}","preventionTips":["Return flat arrays (key/value pairs) from Lua scripts and functions instead of tables/maps with composite keys.","Audit all EVAL/EVALSHA/FCALL scripts when migrating to Protocol: 3 — shapes that worked under RESP2 can become maps under RESP3.","Keep module commands' reply shapes documented and assert them in integration tests.","Prefer Protocol: 2 for codebases with legacy scripts until they are rewritten."],"tags":["go","redis","resp3","map","lua"],"backgroundTag":"resp-map-unhashable-key","analyzedSha":"c5cad058c72f58370553b48566302303cf8a2e89","analyzedAt":"2026-09-01T06:50:53.388Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}