{"record":{"id":"84cb244596c3f504","repo":"anomalyco/sst","slug":"expect-json-object-close-with","errorCode":null,"errorMessage":"expect JSON object close with '}'","messagePattern":"expect JSON object close with '\\}'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/util/kv.go","lineNumber":54,"sourceCode":"\t\tkey, ok := t.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"expecting JSON key should be always a string: %T: %v\", t, t)\n\t\t}\n\t\tvar value T\n\t\terr = dec.Decode(&value)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"JSON value can't be decoded: %T: %v\", value, value)\n\t\t}\n\t\t*p = append(*p, KeyValuePair[T]{Key: key, Value: value})\n\t}\n\n\t// must end with a delim token '}'\n\tt, err = dec.Token()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif delim, ok := t.(json.Delim); !ok || delim != '}' {\n\t\treturn fmt.Errorf(\"expect JSON object close with '}'\")\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tt, err = dec.Token()\n\tif err != io.EOF {\n\t\treturn fmt.Errorf(\"expect end of JSON object but got more token: %T: %v or err: %v\", t, t, err)\n\t}\n\n\treturn nil\n\n}\nfunc (p KeyValuePairs[T]) MarshalJSON() ([]byte, error) {\n\tbuf := &bytes.Buffer{}\n\tbuf.Write([]byte{'{'})\n\tfor i, KeyValuePair := range p {\n\t\tbuf.WriteString(fmt.Sprintf(\"%q:\", fmt.Sprintf(\"%v\", KeyValuePair.Key)))","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/anomalyco/sst/blob/a0bd20f762883e72a35caccb4896c42ce5b3f707/internal/util/kv.go#L36-L72","documentation":"After consuming all key/value pairs, UnmarshalJSON reads the closing token and requires it to be the '}' delimiter. Getting anything else means the object was truncated, an array was supplied, or trailing content is structurally invalid.","triggerScenarios":"Truncated JSON body (network cut mid-response) so the decoder hits EOF where '}' was expected; decoding a JSON array; interleaved/broken streaming input.","commonSituations":"HTTP response body cut off by proxy timeouts; partial file reads; payload wrapped in an array at the top level after a producer refactor.","solutions":["Check that the full payload arrives intact — log the raw body and verify it ends with '}'","Fix Content-Length/chunked issues (timeouts, proxies) causing truncation","Ensure the top-level structure is an object, not an array"],"exampleFix":"// before\ndata := readPartial(resp.Body) // truncated stream\njson.Unmarshal(data, &kv) // expect JSON object close with '}'\n// after\nvar buf bytes.Buffer\n_, err := io.Copy(&buf, resp.Body)\nif err != nil { return err } // surface read errors instead\njson.Unmarshal(buf.Bytes(), &kv)","handlingStrategy":"validation","validationCode":"if !json.Valid(raw) {\n    return fmt.Errorf(\"payload is not valid/truncated JSON\")\n}\ntrimmed := bytes.TrimSpace(raw)\nif len(trimmed) > 0 && trimmed[len(trimmed)-1] != '}' {\n    return fmt.Errorf(\"payload does not end with '}'\")\n}","typeGuard":null,"tryCatchPattern":"var kv KeyValuePairs[string]\nif err := json.Unmarshal(raw, &kv); err != nil {\n    if strings.Contains(err.Error(), \"expect JSON object close\") {\n        return fmt.Errorf(\"truncated response; check body transfer: %w\", err)\n    }\n    return err\n}","preventionTips":["Always read the HTTP body to completion (io.Copy) before decoding","Check Content-Length vs received bytes on large payloads","Increase proxy/read timeouts to avoid mid-body truncation"],"tags":["go","json","unmarshal","truncated"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"a0bd20f762883e72a35caccb4896c42ce5b3f707","analyzedAt":"2026-08-30T11:26:00.383Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}