{"record":{"id":"c248cc1f017fa072","repo":"go-kratos/kratos","slug":"invalid-formatting-for-map-key","errorCode":null,"errorMessage":"invalid formatting for map key","messagePattern":"invalid formatting for map key","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"encoding/form/proto_decode.go","lineNumber":25,"sourceCode":"\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"google.golang.org/protobuf/encoding/protojson\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/reflect/protoreflect\"\n\t\"google.golang.org/protobuf/reflect/protoregistry\"\n\t\"google.golang.org/protobuf/types/known/durationpb\"\n\t\"google.golang.org/protobuf/types/known/fieldmaskpb\"\n\t\"google.golang.org/protobuf/types/known/structpb\"\n\t\"google.golang.org/protobuf/types/known/timestamppb\"\n\t\"google.golang.org/protobuf/types/known/wrapperspb\"\n)\n\nconst fieldSeparator = \".\"\n\nvar errInvalidFormatMapKey = errors.New(\"invalid formatting for map key\")\n\n// DecodeValues decode url value into proto message.\nfunc DecodeValues(msg proto.Message, values url.Values) error {\n\tfor key, values := range values {\n\t\tif err := populateFieldValues(msg.ProtoReflect(), strings.Split(key, \".\"), values); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc populateFieldValues(v protoreflect.Message, fieldPath []string, values []string) error {\n\tif len(fieldPath) < 1 {\n\t\treturn errors.New(\"no field path\")\n\t}\n\tif len(values) < 1 {\n\t\treturn errors.New(\"no value provided\")\n\t}","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/go-kratos/kratos/blob/668db92c2c001e9552594ba5a8aede8456af6d7e/encoding/form/proto_decode.go#L7-L43","documentation":"errInvalidFormatMapKey (encoding/form/proto_decode.go:25) is returned by parseURLQueryMapKey via populateMapField when the URL query key addressing a proto map field cannot be split into (field, key). Accepted forms are 'field[key]=value' (brackets wrapping the whole tail: startIndex>0, endIndex after it, nothing beyond ']') or exactly one separator as in 'field.key=value'. Everything else - no bracket and dot-count != 1, empty field part, bracket at position 0, '[' after ']', trailing characters after ']', unmatched/multiple brackets - fails.","triggerScenarios":"Form/query binding a proto message with a map field where the query contains keys like 'map[]', 'map[', 'kratos]', '[[]', 'map.kratos.v2' (two dots, no brackets), '.kratos' (empty field), or 'map[kratos]=' style malformed tails. The map branch is entered when the field descriptor is a map and fieldPath length is 2 (line 51) or on the post-subfield path (line 58), then strings.Join(fieldPath, \".\") is re-parsed by parseURLQueryMapKey.","commonSituations":"Hand-built query strings from templates or clients that URL-encode brackets wrongly; using both dot nesting and bracket keys in the same key ('a.b[c]'); nested message paths whose second element is the map key written with extra dots; migrating from go-playground/form style keys with subtle syntax drift (the code comments reference that format).","solutions":["Use the canonical bracket form for map fields: 'mymap[key]=value'","Or the single-dot form with exactly one dot: 'mymap.key=value'","Avoid mixing nested message dots and bracket keys in one key; for a map inside a message use 'msg.field[key]=value' so fieldPath length 2 hits the map branch","Sanitize/validate incoming query keys before DecodeValues when keys come from untrusted clients"],"exampleFix":"// before\nvalues := url.Values{}\nvalues.Set(\"labels.meta.app\", \"kratos\") // two dots, no brackets -> invalid\nform.DecodeValues(msg, values)\n\n// after\nvalues := url.Values{}\nvalues.Set(\"labels[meta.app]\", \"kratos\") // field[key]\n// or exactly one dot:\nvalues.Set(\"labels.meta\", \"kratos\")","handlingStrategy":"validation","validationCode":"var mapKeyRe = regexp.MustCompile(`^[^\\[\\].]+(\\[[^\\[\\]]*\\])?$`)\n\nfunc validQueryKeys(q url.Values) error {\n    for k := range q {\n        if !mapKeyRe.MatchString(k) && strings.Count(k, \".\") > 1 {\n            return fmt.Errorf(\"suspicious map key %q\", k)\n        }\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Emit map fields as 'field[key]=value' or exactly 'field.key=value' - never both notations in one key","Encode brackets properly when clients build query strings programmatically","Add contract tests that bind representative query strings to proto fixtures covering map fields"],"tags":["encoding","form","proto","query-string","map"],"backgroundTag":null,"analyzedSha":"668db92c2c001e9552594ba5a8aede8456af6d7e","analyzedAt":"2026-08-16T02:07:20.704Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}