{"record":{"id":"b78089ed6fdffe0c","repo":"anomalyco/sst","slug":"expect-json-object-open-with","errorCode":null,"errorMessage":"expect JSON object open with '{'","messagePattern":"expect JSON object open with '\\{'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/util/kv.go","lineNumber":27,"sourceCode":"\ntype KeyValuePair[T any] struct {\n\tKey   string\n\tValue T\n}\ntype KeyValuePairs[T any] []KeyValuePair[T]\n\nfunc (p *KeyValuePairs[T]) UnmarshalJSON(data []byte) error {\n\t*p = make(KeyValuePairs[T], 0)\n\tdec := json.NewDecoder(bytes.NewReader(data))\n\tdec.UseNumber()\n\n\t// must open 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 open with '{'\")\n\t}\n\n\tfor dec.More() {\n\t\tt, err = dec.Token()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\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})","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/anomalyco/sst/blob/a0bd20f762883e72a35caccb4896c42ce5b3f707/internal/util/kv.go#L9-L45","documentation":"KeyValuePairs.UnmarshalJSON parses a JSON object incrementally with json.Decoder tokens. If the first token is not the '{' delimiter — i.e. the input is an array, string, number, or malformed — it returns this error. It enforces that the serialized form of KeyValuePairs is a JSON object.","triggerScenarios":"Unmarshaling a JSON array (e.g. '[{\"k\":\"v\"}]'), a bare string/number, or empty/garbage input into a KeyValuePairs[T] field.","commonSituations":"API payload changed shape from object to array; feeding a JSON lines file into json.Unmarshal; a producer wrote an array of pairs while the consumer expects an object; corrupted/truncated JSON body.","solutions":["Ensure the input is a JSON object like {\"key\":\"value\"}, not an array","Check the producer/API contract and fix the payload shape","Validate JSON with a parser before unmarshaling to catch truncation"],"exampleFix":"// before\nvar kv KeyValuePairs[string]\njson.Unmarshal([]byte(`[{\"k\":\"v\"}]`), &kv)\n// after\nvar kv KeyValuePairs[string]\njson.Unmarshal([]byte(`{\"k\":\"v\"}`), &kv)","handlingStrategy":"validation","validationCode":"var probe any\nif err := json.Unmarshal(raw, &probe); err != nil {\n    return fmt.Errorf(\"invalid JSON: %w\", err)\n}\nif m, ok := probe.(map[string]any); !ok {\n    return fmt.Errorf(\"expected JSON object, got %T\", probe)\n}","typeGuard":"func isJSONObject(raw []byte) bool {\n    var m map[string]any\n    return json.Unmarshal(raw, &m) == nil && m != nil\n}","tryCatchPattern":"var kv KeyValuePairs[string]\nif err := json.Unmarshal(raw, &kv); err != nil {\n    if strings.Contains(err.Error(), \"expect JSON object open\") {\n        return fmt.Errorf(\"payload must be a JSON object: %w\", err)\n    }\n    return err\n}","preventionTips":["Validate payloads against a JSON schema before unmarshal","Log the raw body when decoding fails to spot shape mismatches","Keep producers and consumers on a shared contract/schema definition"],"tags":["go","json","unmarshal"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"a0bd20f762883e72a35caccb4896c42ce5b3f707","analyzedAt":"2026-08-30T11:26:00.383Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}