{"record":{"id":"63da52506e12c96d","repo":"grpc-ecosystem/grpc-gateway","slug":"json-structure-did-not-match-request-type","errorCode":null,"errorMessage":"JSON structure did not match request type","messagePattern":"JSON structure did not match request type","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"runtime/fieldmask.go","lineNumber":48,"sourceCode":"\t\tif errors.Is(err, io.EOF) {\n\t\t\treturn fm, nil\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tqueue := []fieldMaskPathItem{{node: root, msg: msg.ProtoReflect()}}\n\tfor len(queue) > 0 {\n\t\t// dequeue an item\n\t\titem := queue[0]\n\t\tqueue = queue[1:]\n\n\t\tm, ok := item.node.(map[string]interface{})\n\t\tswitch {\n\t\tcase ok && len(m) > 0:\n\t\t\t// if the item is an object, then enqueue all of its children\n\t\t\tfor k, v := range m {\n\t\t\t\tif item.msg == nil {\n\t\t\t\t\treturn nil, errors.New(\"JSON structure did not match request type\")\n\t\t\t\t}\n\n\t\t\t\tfd := getFieldByName(item.msg.Descriptor().Fields(), k)\n\t\t\t\tif fd == nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"could not find field %q in %q\", k, item.msg.Descriptor().FullName())\n\t\t\t\t}\n\n\t\t\t\tif isDynamicProtoMessage(fd.Message()) {\n\t\t\t\t\tfor _, p := range buildPathsBlindly(string(fd.FullName().Name()), v) {\n\t\t\t\t\t\tnewPath := p\n\t\t\t\t\t\tif item.path != \"\" {\n\t\t\t\t\t\t\tnewPath = item.path + \".\" + newPath\n\t\t\t\t\t\t}\n\t\t\t\t\t\tqueue = append(queue, fieldMaskPathItem{path: newPath})\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/grpc-ecosystem/grpc-gateway/blob/a58a4436a376a4bcc7d8f10c4d4f919a8438bba9/runtime/fieldmask.go#L30-L66","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (proto: message { string name = 1; Sub sub = 2; } Sub { string x = 1; })\n{\"sub\": {\"x\": {\"nested\": \"oops\"}}} // x is a scalar but body nests an object\n// after\n{\"sub\": {\"x\": \"value\"}}","handlingStrategy":"try-catch","validationCode":"// client-side: verify body keys match proto fields before sending\nfd := getFieldByName(msg.Descriptor().Fields(), jsonKey)\nif fd == nil {\n    return fmt.Errorf(\"key %q not present in %s\", jsonKey, msg.Descriptor().FullName())\n}","typeGuard":"func matchesMessageType(body map[string]interface{}, msg proto.Message) bool {\n    fields := msg.Descriptor().Fields()\n    for k, v := range body {\n        fd := getFieldByName(fields, k)\n        if fd == nil { return false }\n        if m, ok := v.(map[string]interface{}); ok && fd.Kind() != protoreflect.MessageKind { return false }\n    }\n    return true\n}","tryCatchPattern":"paths, err := runtime.FieldMaskFromRequestBody(r.Body, msg)\nif err != nil {\n    http.Error(w, fmt.Sprintf(\"invalid request body for %s: %v\", msg.Descriptor().FullName(), err), http.StatusBadRequest)\n    return\n}","preventionTips":["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."],"tags":["grpc-gateway","fieldmask","json","http-handler"],"backgroundTag":"request-body-schema-mismatch","analyzedSha":"a58a4436a376a4bcc7d8f10c4d4f919a8438bba9","analyzedAt":"2026-09-02T10:28:31.537Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T16:17:10.729Z"}