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

  1. Edit the input so `paths` is a JSON object, e.g. "paths": {"/pets": {...}} (use {} for no paths).
  2. If generating JSON from Go/other structs, initialize Paths as a map so it marshals as {} instead of null or [].
  3. Fix whatever produced the spec so it emits a map of path-keyed entries rather than a list.
  4. 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

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.