grpc-ecosystem/grpc-gateway · error
%s: paths: %w
Error message
%s: paths: %w
What it means
This error is returned when the top-level `paths` field of an input document is not a JSON object. parse calls decodeOrderedObject on the raw value to preserve path insertion order; decodeOrderedObject requires the value to be a JSON object mapping path strings to path items, so arrays, strings, numbers, booleans, or null produce a decode error wrapped as `<name>: paths: <cause>`.
Source
Thrown at openapiv3-merge/internal/merge/merge.go:215
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 {
case "openapi":
if err := json.Unmarshal(raw, &d.OpenAPI); err != nil {
return nil, fmt.Errorf("%s: openapi: %w", in.Name, err)
}
case "info":
d.Info = raw
case "servers":
d.Servers = raw
case "paths":
obj, err := decodeOrderedObject(raw)
if err != nil {
return nil, fmt.Errorf("%s: paths: %w", in.Name, err)
}
d.Paths = obj
case "webhooks":
obj, err := decodeOrderedObject(raw)
if err != nil {
return nil, fmt.Errorf("%s: webhooks: %w", in.Name, err)
}
d.Webhooks = obj
case "components":
if !isJSONNull(raw) {
if err := json.Unmarshal(raw, d.Components); err != nil {
return nil, fmt.Errorf("%s: components: %w", in.Name, err)
}
}
case "security":
if err := json.Unmarshal(raw, &d.Security); err != nil {
return nil, fmt.Errorf("%s: security: %w", in.Name, err)
}View on GitHub (pinned to a58a4436a3)
Solutions
- Edit the input so `paths` is a JSON object, e.g. "paths": {"/pets": {...}} (use {} for no paths).
- If generating JSON from Go/other structs, initialize Paths as a map so it marshals as {} instead of null or [].
- Fix whatever produced the spec so it emits a map of path-keyed entries rather than a list.
- Pre-validate: decode the `paths` value into map[string]json.RawMessage before calling Merge to catch it early.
Example fix
// before
{"openapi": "3.1.0", "paths": []}
// after
{"openapi": "3.1.0", "paths": {"/health": {"get": {"responses": {"200": {"description": "ok"}}}}} Defensive patterns
Strategy: validation
Validate before calling
func validatePathsField(data []byte) error {
var doc struct {
Paths json.RawMessage `json:"paths"`
}
if err := json.Unmarshal(data, &doc); err != nil {
return err
}
if len(doc.Paths) == 0 || string(doc.Paths) == "null" {
return nil
}
var m map[string]json.RawMessage
if err := json.Unmarshal(doc.Paths, &m); err != nil {
return fmt.Errorf("paths must be a JSON object: %w", err)
}
return nil
} Type guard
func isJSONObject(raw json.RawMessage) bool {
var m map[string]json.RawMessage
return len(raw) > 0 && json.Unmarshal(raw, &m) == nil
} Try / catch
merged, err := merger.Merge(inputs)
if err != nil {
if strings.Contains(err.Error(), ": paths: ") {
return fmt.Errorf("input spec has a non-object paths field: %w", err)
}
return err
} Prevention
- Initialize paths as {} (or an empty map) when generating specs, never null or [].
- Validate specs against the OpenAPI 3.x JSON Schema before merging.
- Keep paths as a keyed map end-to-end; never convert to arrays during transformation.
- Lint specs in CI for top-level field types before running the merge step.
When it happens
Trigger: Calling Merge with an Input whose Data has `"paths": []` (array), `"paths": "..."`, `"paths": null`, or any non-object value under the `paths` key.
Common situations: Specs serialized from structs where Paths was an uninitialized nil slice (marshals to null or []); template scaffolds with a placeholder array; tools emitting `paths` as a list of path descriptors instead of a map; hand-edits that replaced the object with an empty array.
Related errors
AI-assisted analysis of grpc-ecosystem/grpc-gateway@a58a4436a3 (2026-09-02).
Data as JSON: /api/errors/31e7bb1352fc739f.
Report an issue: GitHub.