grpc-ecosystem/grpc-gateway · error
JSON structure did not match request type
Error message
JSON structure did not match request type
What it means
FieldMaskFromRequestBody walks a JSON request body and the corresponding proto message descriptor in parallel to build a fieldmask path list. When it encounters a JSON object (map) to enqueue but the queue item's associated proto message is nil — i.e. the JSON nesting does not line up with the proto message structure — it returns "JSON structure did not match request type". This means the request body's shape does not correspond to the proto request message being decoded.
Source
Thrown at runtime/fieldmask.go:48
if errors.Is(err, io.EOF) {
return fm, nil
}
return nil, err
}
queue := []fieldMaskPathItem{{node: root, msg: msg.ProtoReflect()}}
for len(queue) > 0 {
// dequeue an item
item := queue[0]
queue = queue[1:]
m, ok := item.node.(map[string]interface{})
switch {
case ok && len(m) > 0:
// if the item is an object, then enqueue all of its children
for k, v := range m {
if item.msg == nil {
return nil, errors.New("JSON structure did not match request type")
}
fd := getFieldByName(item.msg.Descriptor().Fields(), k)
if fd == nil {
return nil, fmt.Errorf("could not find field %q in %q", k, item.msg.Descriptor().FullName())
}
if isDynamicProtoMessage(fd.Message()) {
for _, p := range buildPathsBlindly(string(fd.FullName().Name()), v) {
newPath := p
if item.path != "" {
newPath = item.path + "." + newPath
}
queue = append(queue, fieldMaskPathItem{path: newPath})
}
continue
}
View on GitHub (pinned to a58a4436a3)
Solutions
- Make the JSON body exactly match the proto request message structure: every nested JSON object must correspond to a proto message field.
- Regenerate/re-fetch the client from the current proto definitions so field names and nesting line up.
- Flatten the body: remove extra nesting levels for fields that are scalars in the proto.
- Log the request body and compare with the message descriptor (e.g. via protoc --decode) to find the first divergent key.
Example fix
// before (proto: message { string name = 1; Sub sub = 2; } Sub { string x = 1; })
{"sub": {"x": {"nested": "oops"}}} // x is a scalar but body nests an object
// after
{"sub": {"x": "value"}} Defensive patterns
Strategy: try-catch
Validate before calling
// client-side: verify body keys match proto fields before sending
fd := getFieldByName(msg.Descriptor().Fields(), jsonKey)
if fd == nil {
return fmt.Errorf("key %q not present in %s", jsonKey, msg.Descriptor().FullName())
} Type guard
func matchesMessageType(body map[string]interface{}, msg proto.Message) bool {
fields := msg.Descriptor().Fields()
for k, v := range body {
fd := getFieldByName(fields, k)
if fd == nil { return false }
if m, ok := v.(map[string]interface{}); ok && fd.Kind() != protoreflect.MessageKind { return false }
}
return true
} Try / catch
paths, err := runtime.FieldMaskFromRequestBody(r.Body, msg)
if err != nil {
http.Error(w, fmt.Sprintf("invalid request body for %s: %v", msg.Descriptor().FullName(), err), http.StatusBadRequest)
return
} Prevention
- Regenerate clients whenever proto request messages change.
- Keep JSON body nesting exactly aligned with the proto message tree.
- Add contract tests comparing sample request bodies against descriptors.
When it happens
Trigger: POST/PATCH handlers generated by grpc-gateway (request_*Service_*_0 wrappers) call FieldMaskFromRequestBody when the HTTP body contains update_mask semantics; sending a JSON body whose nested objects do not match the proto message fields (e.g. an object where the proto expects a scalar, or extra nesting), or the field mask walk reaching an object whose corresponding field is not itself a message.
Common situations: Client sends mismatched JSON for the request type after API schema changes; protobuf field renamed/re-typed so JSON keys nest differently; manually crafting PATCH bodies with deeper nesting than the proto allows; using a wrapper/well-known type field where the walk expects a regular message.
Related errors
- unable to marshal non proto field
- unable to unmarshal non proto field
- empty MIME type
- not match to the path pattern
- invalid pattern
AI-assisted analysis of grpc-ecosystem/grpc-gateway@a58a4436a3 (2026-09-02).
Data as JSON: /api/errors/63da52506e12c96d.
Report an issue: GitHub.