{"record":{"id":"81cdd25be310d623","repo":"anomalyco/sst","slug":"expect-end-of-json-object-but-got-more-token-t","errorCode":null,"errorMessage":"expect end of JSON object but got more token: %T: %v or err: %v","messagePattern":"expect end of JSON object but got more token: %T: (.+?) or err: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/util/kv.go","lineNumber":62,"sourceCode":"\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)))\n\t\tencoder := json.NewEncoder(buf)\n\t\tencoder.SetEscapeHTML(false)\n\t\terr := encoder.Encode(KeyValuePair.Value)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif i < len(p)-1 {\n\t\t\tbuf.Write([]byte{','})","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/anomalyco/sst/blob/a0bd20f762883e72a35caccb4896c42ce5b3f707/internal/util/kv.go#L44-L80","documentation":"Once the closing '}' is consumed, UnmarshalJSON requires the decoder to be at EOF. If another token remains (or a non-EOF error exists), it reports 'expect end of JSON object but got more token'. This rejects concatenated or trailing garbage after the object.","triggerScenarios":"Input with trailing data after the object, e.g. '{\"a\":1}{\"b\":2}' (JSON concatenation/streaming), trailing commas parsed oddly, or appended newline-separated records.","commonSituations":"NDJSON files fed to json.Unmarshal one blob at a time; log collectors concatenating JSON docs; copy-paste errors leaving stray characters after the object.","solutions":["Feed exactly one JSON document per Unmarshal call — split NDJSON by lines first","Trim trailing whitespace/garbage from the payload","Use a json.Decoder loop over multiple documents if the stream intentionally holds several objects"],"exampleFix":"// before\ndec := json.NewDecoder(conn)\nvar kv KeyValuePairs[string]\njson.Unmarshal(buf, &kv) // buf has two concatenated objects\n// after\nlines := bytes.Split(buf, []byte(\"\\n\"))\nfor _, l := range lines {\n    var kv KeyValuePairs[string]\n    json.Unmarshal(l, &kv)\n}","handlingStrategy":"validation","validationCode":"dec := json.NewDecoder(bytes.NewReader(raw))\nif err := dec.Decode(&json.RawMessage{}); err != nil {\n    return err\n}\nif dec.More() {\n    return fmt.Errorf(\"trailing data after first JSON document\")\n}","typeGuard":null,"tryCatchPattern":"var kv KeyValuePairs[string]\nif err := json.Unmarshal(raw, &kv); err != nil {\n    if strings.Contains(err.Error(), \"expect end of JSON object\") {\n        return fmt.Errorf(\"multiple/trailing JSON documents; split NDJSON first\")\n    }\n    return err\n}","preventionTips":["Split NDJSON/concatenated JSON into individual documents before unmarshal","Trim trailing whitespace and garbage from external input","Use json.Decoder in a loop when streams intentionally contain multiple documents"],"tags":["go","json","unmarshal","trailing-data"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"a0bd20f762883e72a35caccb4896c42ce5b3f707","analyzedAt":"2026-08-30T11:26:00.383Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}