{"record":{"id":"ae9f161bd90087e9","repo":"go-kratos/kratos","slug":"field-already-set-for-oneof-q","errorCode":null,"errorMessage":"field already set for oneof %q","messagePattern":"field already set for oneof %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"encoding/form/proto_decode.go","lineNumber":69,"sourceCode":"\t\tif fd.IsMap() && len(fieldPath) == 2 {\n\t\t\treturn populateMapField(fd, v.Mutable(fd).Map(), fieldPath, values)\n\t\t}\n\t\tif i == len(fieldPath)-1 {\n\t\t\tbreak\n\t\t}\n\t\tif fd.Message() == nil || fd.Cardinality() == protoreflect.Repeated {\n\t\t\tif fd.IsMap() && len(fieldPath) > 1 {\n\t\t\t\t// post subfield\n\t\t\t\treturn populateMapField(fd, v.Mutable(fd).Map(), []string{fieldPath[1]}, values)\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"invalid path: %q is not a message\", fieldName)\n\t\t}\n\n\t\tv = v.Mutable(fd).Message()\n\t}\n\tif of := fd.ContainingOneof(); of != nil {\n\t\tif f := v.WhichOneof(of); f != nil {\n\t\t\treturn fmt.Errorf(\"field already set for oneof %q\", of.FullName().Name())\n\t\t}\n\t}\n\tswitch {\n\tcase fd.IsList():\n\t\treturn populateRepeatedField(fd, v.Mutable(fd).List(), values)\n\tcase fd.IsMap():\n\t\treturn populateMapField(fd, v.Mutable(fd).Map(), fieldPath, values)\n\t}\n\tif len(values) > 1 {\n\t\treturn fmt.Errorf(\"too many values for field %q: %s\", fd.FullName().Name(), strings.Join(values, \", \"))\n\t}\n\treturn populateField(fd, v, values[0])\n}\n\nfunc getFieldDescriptor(v protoreflect.Message, fieldName string) protoreflect.FieldDescriptor {\n\tvar (\n\t\tfields = v.Descriptor().Fields()\n\t\tfd     = getDescriptorByFieldAndName(fields, fieldName)","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/go-kratos/kratos/blob/668db92c2c001e9552594ba5a8aede8456af6d7e/encoding/form/proto_decode.go#L51-L87","documentation":"Form-binding decoder error raised after all form values for a path were collected: the target singular field belongs to a protobuf oneof, and a different member of that same oneof was already set earlier in the request. Because a oneof can hold only one value, populating a second member is rejected instead of silently overwriting.","triggerScenarios":"A query/form request that supplies two parameters mapping to different members of the same oneof, e.g. proto `oneof filter { string name = 1; int32 id = 2; }` with query `?filter.name=x&filter.id=5` (or flat `?name=x&id=5` depending on descriptor lookup). Both keys resolve, the first sets the oneof, the second triggers WhichOneof != nil and this error.","commonSituations":"Frontend forms that always send all optional filter fields; query-string builders that serialize empty defaults instead of omitting unset oneof members; API consumers unaware that mutually-exclusive fields share a oneof after a proto refactor.","solutions":["Send only one member of each oneof per request; omit the other key entirely (not empty string, which still counts for some kinds)","On the client, build the query by skipping zero-value oneof members before serializing","If both values are legitimately needed at once, change the proto: split them into independent fields instead of a oneof","If you own the server and want last-write-wins semantics, decode into a custom struct or pre-sanitize the url.Values to keep only the last member per oneof before form binding"],"exampleFix":"// proto: oneof by { string name = 1; uint64 id = 2; }\n// before: GET /users?by.name=ken&by.id=7 -> field already set for oneof \"by\"\n// after:  GET /users?by.id=7","handlingStrategy":"validation","validationCode":"// Keep only one member per oneof before binding\nfunc dedupeOneofs(v url.Values, msg protoreflect.Message) url.Values {\n\tout := url.Values{}\n\tseen := map[protoreflect.FullName]string{}\n\tfor _, key := range sortedKeys(v) { // deterministic order: first wins\n\t\tfd := lookupField(msg, key)\n\t\tif fd != nil {\n\t\t\tif of := fd.ContainingOneof(); of != nil {\n\t\t\t\tif prev, dup := seen[of.FullName()]; dup {\n\t\t\t\t\t_ = prev\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tseen[of.FullName()] = key\n\t\t\t}\n\t\t}\n\t\tout[key] = v[key]\n\t}\n\treturn out\n}","typeGuard":"func oneofMembers(msg protoreflect.Message) map[string]string {\n\t// map query-key -> oneof full name, used client-side to avoid sending two\n\tmembers := map[string]string{}\n\tmsg.Descriptor().Fields().Range(func(fd protoreflect.FieldDescriptor) bool {\n\t\tif of := fd.ContainingOneof(); of != nil {\n\t\t\tmembers[string(fd.Name())] = string(of.FullName())\n\t\t}\n\t\treturn true\n\t})\n\treturn members\n}","tryCatchPattern":"if err := binding.BindQuery(msg, q); err != nil {\n\tif strings.Contains(err.Error(), \"field already set for oneof\") {\n\t\treturn errors.BadRequest(\"ONEOF_CONFLICT\", \"send only one of the mutually exclusive fields: \"+err.Error())\n\t}\n}","preventionTips":["Client: omit unset oneof members entirely instead of sending empty strings","Document oneof fields as mutually exclusive in the API surface (OpenAPI oneOf)","Build query params from the set oneof member only (switch on msg.WhichOneof)","Reject duplicate oneof keys at the gateway before they reach the form codec"],"tags":["go","kratos","form-binding","protobuf","oneof","validation"],"backgroundTag":null,"analyzedSha":"668db92c2c001e9552594ba5a8aede8456af6d7e","analyzedAt":"2026-08-16T02:07:20.704Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}