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

  1. Make the JSON body exactly match the proto request message structure: every nested JSON object must correspond to a proto message field.
  2. Regenerate/re-fetch the client from the current proto definitions so field names and nesting line up.
  3. Flatten the body: remove extra nesting levels for fields that are scalars in the proto.
  4. 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

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


AI-assisted analysis of grpc-ecosystem/grpc-gateway@a58a4436a3 (2026-09-02). Data as JSON: /api/errors/63da52506e12c96d. Report an issue: GitHub.