{"record":{"id":"f97dbc2f224a9e5d","repo":"hashicorp/terraform","slug":"input-must-be-a-map-of-lists-of-strings","errorCode":null,"errorMessage":"input must be a map of lists of strings","messagePattern":"input must be a map of lists of strings","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/lang/funcs/collection.go","lineNumber":594,"sourceCode":"\t\tif !inputMap.IsWhollyKnown() {\n\t\t\treturn cty.UnknownVal(retType), nil\n\t\t}\n\n\t\toutputMap := make(map[string]cty.Value)\n\t\ttmpMap := make(map[string][]string)\n\n\t\tfor it := inputMap.ElementIterator(); it.Next(); {\n\t\t\tinKey, inVal := it.Element()\n\t\t\tif inVal.IsNull() {\n\t\t\t\treturn cty.MapValEmpty(cty.List(cty.String)), errors.New(\"input must not contain null list\")\n\t\t\t}\n\t\t\tfor iter := inVal.ElementIterator(); iter.Next(); {\n\t\t\t\t_, val := iter.Element()\n\t\t\t\tif val.IsNull() {\n\t\t\t\t\treturn cty.MapValEmpty(cty.List(cty.String)), errors.New(\"input list must not contain null string\")\n\t\t\t\t}\n\t\t\t\tif !val.Type().Equals(cty.String) {\n\t\t\t\t\treturn cty.MapValEmpty(cty.List(cty.String)), errors.New(\"input must be a map of lists of strings\")\n\t\t\t\t}\n\n\t\t\t\toutKey := val.AsString()\n\t\t\t\tif _, ok := tmpMap[outKey]; !ok {\n\t\t\t\t\ttmpMap[outKey] = make([]string, 0)\n\t\t\t\t}\n\t\t\t\toutVal := tmpMap[outKey]\n\t\t\t\toutVal = append(outVal, inKey.AsString())\n\t\t\t\tsort.Strings(outVal)\n\t\t\t\ttmpMap[outKey] = outVal\n\t\t\t}\n\t\t}\n\n\t\tfor outKey, outVal := range tmpMap {\n\t\t\tvalues := make([]cty.Value, 0)\n\t\t\tfor _, v := range outVal {\n\t\t\t\tvalues = append(values, cty.StringVal(v))\n\t\t\t}","sourceCodeStart":576,"sourceCodeEnd":612,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/lang/funcs/collection.go#L576-L612","documentation":"Thrown by TransposeFunc.Impl when an inner element is non-null but its type is not cty.String. transpose() is declared to take map(list(string)) and re-verifies each element is a string; a non-string element violates the contract.","triggerScenarios":"transpose({a = [1, 2]}) (numbers), transpose({a = [true]}); a map whose lists contain numbers or bools due to loose typing upstream.","commonSituations":"Numeric IDs where strings were expected; a data structure whose element type wasn't coerced; JSON decode producing numbers.","solutions":["Stringify each element: { for k, v in var.m : k => [for s in v : tostring(s)] }.","Ensure the variable is typed map(list(string)) so Terraform coerces at the boundary.","Build the input from string literals/attributes only."],"exampleFix":"# before\nlocals { t = transpose(var.groups) }   # numbers inside -> error\n# after\nlocals {\n  strmap = { for k, v in var.groups : k => [for s in v : tostring(s)] }\n  t      = transpose(local.strmap)\n}","handlingStrategy":"type-guard","validationCode":"# coerce every inner element to string before transpose\nlocals {\n  strmap = { for k, v in var.groups : k => [for s in v : tostring(s)] }\n  t      = transpose(local.strmap)\n}","typeGuard":"locals {\n  strmap = { for k, v in var.groups : k => [for s in v : tostring(s)] }\n  ok     = alltrue([for _, list in local.strmap : alltrue([for s in list : s != null && can(tostring(s))])])\n  t      = local.ok ? transpose(local.strmap) : {}\n}","tryCatchPattern":"locals { t = try(transpose({ for k, v in var.groups : k => [for s in v : tostring(s)] }), {}) }","preventionTips":["Type variables as map(list(string)) so non-strings are coerced or rejected at validation.","Build inner lists from string literals/attributes only.","Use tostring() defensively when ingesting JSON/numeric data."],"tags":["hcl","function","transpose","type-validation"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}