{"record":{"id":"8ec5d15b75e68632","repo":"grpc-ecosystem/grpc-gateway","slug":"s-w","errorCode":null,"errorMessage":"%s: %w","messagePattern":"%s: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"openapiv3-merge/internal/merge/merge.go","lineNumber":185,"sourceCode":"}\n\n// parse decodes one input into a document, separating known top-level\n// fields from extras. The token-based parser is used instead of\n// json.Unmarshal because we need to capture first-occurrence order of\n// unknown keys and the insertion order of `paths`/`webhooks` entries.\nfunc parse(in Input) (*document, error) {\n\td := &document{\n\t\tname:       in.Name,\n\t\tPaths:      newOrderedObject(),\n\t\tWebhooks:   newOrderedObject(),\n\t\tComponents: &components{},\n\t\textras:     newOrderedObject(),\n\t}\n\tdec := json.NewDecoder(bytes.NewReader(in.Data))\n\tdec.UseNumber()\n\ttok, err := dec.Token()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%s: %w\", in.Name, err)\n\t}\n\tif delim, ok := tok.(json.Delim); !ok || delim != '{' {\n\t\treturn nil, fmt.Errorf(\"%s: expected JSON object at top level\", in.Name)\n\t}\n\tfor dec.More() {\n\t\ttok, err := dec.Token()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%s: %w\", in.Name, err)\n\t\t}\n\t\tkey, ok := tok.(string)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"%s: unexpected token %v\", in.Name, tok)\n\t\t}\n\t\tvar raw json.RawMessage\n\t\tif err := dec.Decode(&raw); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%s: %q: %w\", in.Name, key, err)\n\t\t}\n\t\tswitch key {","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/grpc-ecosystem/grpc-gateway/blob/a58a4436a376a4bcc7d8f10c4d4f919a8438bba9/openapiv3-merge/internal/merge/merge.go#L167-L203","documentation":"This error wraps a low-level JSON decoding failure encountered while reading the very first token of an input document in openapiv3-merge's parse step (merge.go:185). The library streams each input with encoding/json's token decoder, so any syntax error before the first token (e.g. malformed bytes, BOM issues, encoding problems) surfaces here, wrapped with the input's name via %w so the underlying json error is preserved for errors.Is/As. It means the library never got far enough to even see whether the document is an object.","triggerScenarios":"Calling Merge with an Input whose Data is not valid JSON at all — e.g. empty bytes, a YAML file passed as-is, a truncated download, or a file starting with a UTF-8 BOM or stray character — causes dec.Token() to fail immediately at merge.go:183-185.","commonSituations":"Passing a .yaml/.yml OpenAPI spec to a JSON-only merger; CI fetching a spec that returned an HTML error page or empty body; an editor saving the spec with a BOM; a partially written or truncated spec file in a build pipeline.","solutions":["Validate the input with json.Valid(in.Data) before calling Merge and fail fast with a clear filename-scoped message.","Convert YAML (or other non-JSON) specs to JSON first (e.g. gopkg.in/yaml.v3 + json.Marshal) before passing them to Merge.","Strip any UTF-8 BOM: bytes.TrimPrefix(data, []byte{0xEF,0xBB,0xBF}).","If the spec came from a network fetch, check the response status and content type and log the first ~100 bytes to spot HTML/empty payloads.","Inspect the wrapped underlying error (errors.Unwrap / %v of the message) — it pinpoints the exact byte offset of the syntax error."],"exampleFix":"// before\nmerged, err := merge.Merge([]merge.Input{{Name: path, Data: raw}})\n// after\nif !json.Valid(raw) {\n    return fmt.Errorf(\"%s is not valid JSON\", path)\n}\nraw = bytes.TrimPrefix(raw, []byte{0xEF, 0xBB, 0xBF})\nmerged, err := merge.Merge([]merge.Input{{Name: path, Data: raw}})","handlingStrategy":"validation","validationCode":"func ensureJSONObject(name string, data []byte) error {\n    data = bytes.TrimPrefix(data, []byte{0xEF, 0xBB, 0xBF})\n    if len(bytes.TrimSpace(data)) == 0 {\n        return fmt.Errorf(\"%s: empty input\", name)\n    }\n    if !json.Valid(data) {\n        return fmt.Errorf(\"%s: not valid JSON\", name)\n    }\n    return nil\n}","typeGuard":"func isJSONObject(data []byte) bool {\n    dec := json.NewDecoder(bytes.NewReader(bytes.TrimPrefix(data, []byte{0xEF, 0xBB, 0xBF})))\n    tok, err := dec.Token()\n    if err != nil { return false }\n    d, ok := tok.(json.Delim)\n    return ok && d == '{'\n}","tryCatchPattern":null,"preventionTips":["Always run json.Valid on every input before Merge.","Convert YAML/JSON5 sources to strict JSON in a preprocessing step.","Strip UTF-8 BOMs when reading files produced on Windows.","When fetching specs over HTTP, assert status 200 and a JSON content type before merging.","Log the first 100 bytes of any failing input to spot HTML/empty payloads quickly."],"tags":["json","parsing","openapi","go"],"backgroundTag":"invalid-json-syntax","analyzedSha":"a58a4436a376a4bcc7d8f10c4d4f919a8438bba9","analyzedAt":"2026-09-02T10:28:31.537Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T16:17:10.729Z"}