{"record":{"id":"07745b9aaf79f5e9","repo":"grpc/grpc-go","slug":"json-unmarshal-v-v","errorCode":null,"errorMessage":"json.Unmarshal(%v): %v","messagePattern":"json\\.Unmarshal\\((.+?)\\): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"credentials/sts/sts.go","lineNumber":326,"sourceCode":"\t// responsibility of the caller to read the response body till an EOF is\n\t// encountered and to close it.\n\tbody, err := io.ReadAll(resp.Body)\n\tresp.Body.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif resp.StatusCode == http.StatusOK {\n\t\treturn body, nil\n\t}\n\tlogger.Warningf(\"http status %d, body: %s\", resp.StatusCode, string(body))\n\treturn nil, fmt.Errorf(\"http status %d, body: %s\", resp.StatusCode, string(body))\n}\n\nfunc tokenInfoFromResponse(respBody []byte) (*tokenInfo, error) {\n\trespData := &responseParameters{}\n\tif err := json.Unmarshal(respBody, respData); err != nil {\n\t\treturn nil, fmt.Errorf(\"json.Unmarshal(%v): %v\", respBody, err)\n\t}\n\tif respData.AccessToken == \"\" {\n\t\treturn nil, fmt.Errorf(\"empty accessToken in response (%v)\", string(respBody))\n\t}\n\treturn &tokenInfo{\n\t\ttokenType:  respData.TokenType,\n\t\ttoken:      respData.AccessToken,\n\t\texpiryTime: time.Now().Add(time.Duration(respData.ExpiresIn) * time.Second),\n\t}, nil\n}\n\n// requestParameters stores all STS request attributes defined in\n// https://tools.ietf.org/html/rfc8693#section-2.1.\ntype requestParameters struct {\n\t// REQUIRED. The value \"urn:ietf:params:oauth:grant-type:token-exchange\"\n\t// indicates that a token exchange is being performed.\n\tGrantType string `json:\"grant_type\"`\n\t// OPTIONAL. Indicates the location of the target service or resource where","sourceCodeStart":308,"sourceCodeEnd":344,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/credentials/sts/sts.go#L308-L344","documentation":"Returned by tokenInfoFromResponse in sts/sts.go:326 when json.Unmarshal of the STS response body into responseParameters fails. The STS server returned HTTP 200 but a body that is not valid JSON for the expected fields (it logs/returns the raw bytes via %v).","triggerScenarios":"The token-exchange endpoint returned an HTML error page, a plain-text message, or a JSON shape that does not deserialize into responseParameters, despite a 200 status; a proxy/CDN intercepting the response.","commonSituations":"Corporate proxy returns a 200 + HTML captive-portal page; STS endpoint behind an API gateway returning a generic success page; misconfigured endpoint URL that hits a different service; version skew where the server returns a newer incompatible schema.","solutions":["Inspect the raw body in the error message to identify what was actually returned.","Confirm TokenExchangeServiceURI points at the real STS token endpoint, not a generic web frontend.","Bypass any intercepting proxy for the token-exchange host.","If the server legitimately returns a different JSON shape, switch to a custom PerRPCCredentials implementation."],"exampleFix":"// before\nopts := sts.Options{TokenExchangeServiceURI: \"https://gateway.example.com/\"} // returns HTML\n\n// after\nopts := sts.Options{TokenExchangeServiceURI: \"https://sts.example.com/v1/token\"} // returns JSON","handlingStrategy":"validation","validationCode":"// Sanity-check the STS endpoint returns JSON before relying on it.\nresp, err := http.Post(opts.TokenExchangeServiceURI, \"application/json\", bytes.NewReader(sampleBody))\nif err != nil { return err }\ndefer resp.Body.Close()\nif ct := resp.Header.Get(\"Content-Type\"); !strings.Contains(ct, \"application/json\") {\n    return fmt.Errorf(\"STS endpoint returned non-JSON (%s)\", ct)\n}","typeGuard":null,"tryCatchPattern":"if strings.Contains(err.Error(), \"json.Unmarshal\") {\n    // STS endpoint returned a non-JSON body (HTML page, wrong service, proxy)\n    // inspect the raw bytes in the error and point the URI at the real token endpoint\n}","preventionTips":["Confirm the STS URI points at the real token endpoint, not a gateway frontend.","Bypass intercepting proxies for the token-exchange host.","Add a startup connectivity probe that asserts a JSON content-type response."],"tags":["sts","serialization","network","json","go"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}