grpc-ecosystem/grpc-gateway · error
%s: %w
Error message
%s: %w
What it means
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.
Source
Thrown at openapiv3-merge/internal/merge/merge.go:185
}
// parse decodes one input into a document, separating known top-level
// fields from extras. The token-based parser is used instead of
// json.Unmarshal because we need to capture first-occurrence order of
// unknown keys and the insertion order of `paths`/`webhooks` entries.
func parse(in Input) (*document, error) {
d := &document{
name: in.Name,
Paths: newOrderedObject(),
Webhooks: newOrderedObject(),
Components: &components{},
extras: newOrderedObject(),
}
dec := json.NewDecoder(bytes.NewReader(in.Data))
dec.UseNumber()
tok, err := dec.Token()
if err != nil {
return nil, fmt.Errorf("%s: %w", in.Name, err)
}
if delim, ok := tok.(json.Delim); !ok || delim != '{' {
return nil, fmt.Errorf("%s: expected JSON object at top level", in.Name)
}
for dec.More() {
tok, err := dec.Token()
if err != nil {
return nil, fmt.Errorf("%s: %w", in.Name, err)
}
key, ok := tok.(string)
if !ok {
return nil, fmt.Errorf("%s: unexpected token %v", in.Name, tok)
}
var raw json.RawMessage
if err := dec.Decode(&raw); err != nil {
return nil, fmt.Errorf("%s: %q: %w", in.Name, key, err)
}
switch key {View on GitHub (pinned to a58a4436a3)
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.
Example fix
// before
merged, err := merge.Merge([]merge.Input{{Name: path, Data: raw}})
// after
if !json.Valid(raw) {
return fmt.Errorf("%s is not valid JSON", path)
}
raw = bytes.TrimPrefix(raw, []byte{0xEF, 0xBB, 0xBF})
merged, err := merge.Merge([]merge.Input{{Name: path, Data: raw}}) Defensive patterns
Strategy: validation
Validate before calling
func ensureJSONObject(name string, data []byte) error {
data = bytes.TrimPrefix(data, []byte{0xEF, 0xBB, 0xBF})
if len(bytes.TrimSpace(data)) == 0 {
return fmt.Errorf("%s: empty input", name)
}
if !json.Valid(data) {
return fmt.Errorf("%s: not valid JSON", name)
}
return nil
} Type guard
func isJSONObject(data []byte) bool {
dec := json.NewDecoder(bytes.NewReader(bytes.TrimPrefix(data, []byte{0xEF, 0xBB, 0xBF})))
tok, err := dec.Token()
if err != nil { return false }
d, ok := tok.(json.Delim)
return ok && d == '{'
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of grpc-ecosystem/grpc-gateway@a58a4436a3 (2026-09-02).
Data as JSON: /api/errors/8ec5d15b75e68632.
Report an issue: GitHub.