go-kratos/kratos · warning

field already set for oneof %q

Error message

field already set for oneof %q

What it means

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.

Source

Thrown at encoding/form/proto_decode.go:69

		if fd.IsMap() && len(fieldPath) == 2 {
			return populateMapField(fd, v.Mutable(fd).Map(), fieldPath, values)
		}
		if i == len(fieldPath)-1 {
			break
		}
		if fd.Message() == nil || fd.Cardinality() == protoreflect.Repeated {
			if fd.IsMap() && len(fieldPath) > 1 {
				// post subfield
				return populateMapField(fd, v.Mutable(fd).Map(), []string{fieldPath[1]}, values)
			}
			return fmt.Errorf("invalid path: %q is not a message", fieldName)
		}

		v = v.Mutable(fd).Message()
	}
	if of := fd.ContainingOneof(); of != nil {
		if f := v.WhichOneof(of); f != nil {
			return fmt.Errorf("field already set for oneof %q", of.FullName().Name())
		}
	}
	switch {
	case fd.IsList():
		return populateRepeatedField(fd, v.Mutable(fd).List(), values)
	case fd.IsMap():
		return populateMapField(fd, v.Mutable(fd).Map(), fieldPath, values)
	}
	if len(values) > 1 {
		return fmt.Errorf("too many values for field %q: %s", fd.FullName().Name(), strings.Join(values, ", "))
	}
	return populateField(fd, v, values[0])
}

func getFieldDescriptor(v protoreflect.Message, fieldName string) protoreflect.FieldDescriptor {
	var (
		fields = v.Descriptor().Fields()
		fd     = getDescriptorByFieldAndName(fields, fieldName)

View on GitHub (pinned to 668db92c2c)

Solutions

  1. Send only one member of each oneof per request; omit the other key entirely (not empty string, which still counts for some kinds)
  2. On the client, build the query by skipping zero-value oneof members before serializing
  3. If both values are legitimately needed at once, change the proto: split them into independent fields instead of a oneof
  4. 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

Example fix

// proto: oneof by { string name = 1; uint64 id = 2; }
// before: GET /users?by.name=ken&by.id=7 -> field already set for oneof "by"
// after:  GET /users?by.id=7
Defensive patterns

Strategy: validation

Validate before calling

// Keep only one member per oneof before binding
func dedupeOneofs(v url.Values, msg protoreflect.Message) url.Values {
	out := url.Values{}
	seen := map[protoreflect.FullName]string{}
	for _, key := range sortedKeys(v) { // deterministic order: first wins
		fd := lookupField(msg, key)
		if fd != nil {
			if of := fd.ContainingOneof(); of != nil {
				if prev, dup := seen[of.FullName()]; dup {
					_ = prev
					continue
				}
				seen[of.FullName()] = key
			}
		}
		out[key] = v[key]
	}
	return out
}

Type guard

func oneofMembers(msg protoreflect.Message) map[string]string {
	// map query-key -> oneof full name, used client-side to avoid sending two
	members := map[string]string{}
	msg.Descriptor().Fields().Range(func(fd protoreflect.FieldDescriptor) bool {
		if of := fd.ContainingOneof(); of != nil {
			members[string(fd.Name())] = string(of.FullName())
		}
		return true
	})
	return members
}

Try / catch

if err := binding.BindQuery(msg, q); err != nil {
	if strings.Contains(err.Error(), "field already set for oneof") {
		return errors.BadRequest("ONEOF_CONFLICT", "send only one of the mutually exclusive fields: "+err.Error())
	}
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of go-kratos/kratos@668db92c2c (2026-08-16). Data as JSON: /api/errors/ae9f161bd90087e9. Report an issue: GitHub.