vitessio/vitess · error

json error

Error message

json error: %v

What it means

vtctl's JSON output helper wraps any failure of json.MarshalIndent when serializing a command's result object. The error means Go's encoding/json could not serialize the value (e.g. an unsupported type such as a channel, func, or complex number, or a cyclic structure). It is thrown so the caller returns a descriptive error instead of an unusable nil data blob.

Solutions

  1. Inspect the command result struct for fields that json cannot encode (func, chan, complex, cycles) and fix or remove them
  2. Add a MarshalJSON method to the offending custom type
  3. If the object should be empty, ensure the []byte path receives a truly empty value so the nil early-return is taken
  4. Re-run the command; if reproducible, capture the underlying %v detail which names the unsupported JSON value

Example fix

// before
type Result struct { Hook func() }
// after
type Result struct { HookName string } // encodable field instead of func
Defensive patterns

Strategy: validation

Validate before calling

if err := json.Marshal(obj); err != nil {
  return nil, fmt.Errorf("result object is not JSON-encodable: %v", err)
}

Type guard

func isJSONEncodable(v any) bool {
  return json.Valid(mustMarshalOrEmpty(v))
}

Try / catch

data, err := jsonOutput(obj)
if err != nil {
  log.Warn("vtctl output marshal failed", slog.Any("error", err))
  return err
}

Prevention

When it happens

Trigger: Calling a vtctl command whose result object is marshaled in the 'case []byte' branch path: len(obj)==0 returns nil early, otherwise json.MarshalIndent(obj, "", " ") fails on an unencodable value.

Common situations: A command result contains a field of an unsupported type (func, chan, complex) or a reference cycle; custom types without MarshalJSON used in vtctl command output; regressions after changing a result struct.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/adbb0a2c10b716ec. Report an issue: GitHub.

Appendix: source

Thrown at go/vt/vtctl/vtctl.go:3986

		// Marshal the protobuf message.
		data, err = protojson.MarshalOptions{
			Multiline:       true,
			Indent:          "  ",
			UseProtoNames:   true,
			UseEnumNumbers:  true,
			EmitUnpopulated: true,
		}.Marshal(obj)
		if err != nil {
			return nil, fmt.Errorf("protojson error: %v", err)
		}
	case []string:
		if len(obj) == 0 {
			return []byte{'[', ']'}, nil
		}
		data, err = json.MarshalIndent(obj, "", "  ")
		if err != nil {
			return nil, fmt.Errorf("json error: %v", err)
		}
	default:
		data, err = json.MarshalIndent(obj, "", "  ")
		if err != nil {
			return nil, fmt.Errorf("json error: %v", err)
		}
	}

	return data, nil
}

// RunCommand will execute the command using the provided wrangler.
// It will return the actionPath to wait on for long remote actions if
// applicable.
func RunCommand(ctx context.Context, wr *wrangler.Wrangler, args []string) error {
	if len(args) == 0 {
		wr.Logger().Printf("No command specified. Please see the list below:\n\n")
		PrintAllCommands(wr.Logger())

View on GitHub (pinned to 01a25a7d17)