{"record":{"id":"f14eca0f6e7d7957","repo":"hashicorp/nomad","slug":"q-map-key-is-not-string-s","errorCode":null,"errorMessage":"%q: map key is not string: %s","messagePattern":"%q: map key is not string: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"helper/flatmap/flatmap.go","lineNumber":65,"sourceCode":"\t\toutput[prefix] = \"nil\"\n\tcase reflect.Pointer:\n\t\tif primitiveOnly && enteredStruct {\n\t\t\treturn\n\t\t}\n\n\t\te := v.Elem()\n\t\tif !e.IsValid() {\n\t\t\toutput[prefix] = \"nil\"\n\t\t}\n\t\tflatten(prefix, e, primitiveOnly, enteredStruct, output)\n\tcase reflect.Map:\n\t\tfor _, k := range v.MapKeys() {\n\t\t\tif k.Kind() == reflect.Interface {\n\t\t\t\tk = k.Elem()\n\t\t\t}\n\n\t\t\tif k.Kind() != reflect.String {\n\t\t\t\tpanic(fmt.Sprintf(\"%q: map key is not string: %s\", prefix, k))\n\t\t\t}\n\n\t\t\tflatten(getSubKeyPrefix(prefix, k.String()), v.MapIndex(k), primitiveOnly, enteredStruct, output)\n\t\t}\n\tcase reflect.Struct:\n\t\tif primitiveOnly && enteredStruct {\n\t\t\treturn\n\t\t}\n\t\tenteredStruct = true\n\n\t\tt := v.Type()\n\t\tfor i := 0; i < v.NumField(); i++ {\n\t\t\tname := t.Field(i).Name\n\t\t\tval := v.Field(i)\n\t\t\tif val.Kind() == reflect.Interface && !val.IsNil() {\n\t\t\t\tval = val.Elem()\n\t\t\t}\n","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/helper/flatmap/flatmap.go#L47-L83","documentation":"helper/flatmap recursively flattens arbitrary Go values into a flat string-keyed map. When traversing a reflect.Map, all keys must be strings (after unwrapping interfaces); a non-string key type cannot be represented in the flat map, so flatten panics.","triggerScenarios":"Calling flatmap.Flatten or flatmap.Diff on a struct/variable payload that contains a map with non-string keys (e.g. map[int]string, map[uuid.UUID]...).","commonSituations":"Task/group payloads or job specs containing maps keyed by integers or custom types passed into the flatmap-based diffing code; user structs handed to Nomad's templating/diff utilities with typed map keys.","solutions":["Convert the map to map[string]interface{} (stringify keys) before passing the value to Flatten/Diff","Change the containing struct so map keys are strings","Stringify keys at the boundary with a helper like fmt.Sprint(k) into a new map"],"exampleFix":"// before\nm := map[int]string{1: \"a\"}\nout := flatmap.Flatten(m) // panics\n// after\nm2 := map[string]interface{}{\"1\": \"a\"}\nout := flatmap.Flatten(m2)","handlingStrategy":"validation","validationCode":"func hasStringKeys(v reflect.Value) bool {\n    if v.Kind() != reflect.Map { return true }\n    for _, k := range v.MapKeys() {\n        if k.Kind() == reflect.Interface { k = k.Elem() }\n        if k.Kind() != reflect.String { return false }\n    }\n    return true\n}\n","typeGuard":"func flattenSafe(v interface{}) (m map[string]string, ok bool) {\n    defer func() {\n        if r := recover(); r != nil { ok = false; m = nil }\n    }()\n    m = flatmap.Flatten(v)\n    return m, true\n}\n","tryCatchPattern":"defer func() {\n    if r := recover(); r != nil {\n        err = fmt.Errorf(\"flatmap: %v\", r)\n    }\n}()","preventionTips":["Use string map keys in structs destined for Flatten/Diff","Validate payloads for non-string map keys before diffing","Keep flatmap inputs limited to API-serialization-friendly types"],"tags":["reflection","flatmap","panic","type-error"],"backgroundTag":"non-string-map-key","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}