matryer/xbar · error

json.MarshalIndent

Error message

json.MarshalIndent

What it means

SaveVariableValues serializes the plugin's variable values with json.MarshalIndent before writing <pluginPath>.vars.json. This error wraps any Marshal failure. Because values are map[string]interface{}, marshaling fails only when a value contains an unsupported type (e.g. a channel, func, complex number, or a struct with unexported/invalid fields) or an invalid UTF-8/radical type that json cannot represent.

Source

Thrown at pkg/plugins/variables.go:23

	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"

	"github.com/matryer/xbar/pkg/metadata"
	"github.com/pkg/errors"
)

// variableJSONFileExt is the extension for the variable JSON payload.
const variableJSONFileExt = ".vars.json"

// SaveVariableValues saves the values for a plugin.
func SaveVariableValues(pluginDir, installedPluginPath string, values map[string]interface{}) error {
	b, err := json.MarshalIndent(values, "", "\t")
	if err != nil {
		return errors.Wrap(err, "json.MarshalIndent")
	}
	filename := filepath.Join(pluginDir, installedPluginPath+variableJSONFileExt)
	err = ioutil.WriteFile(filename, b, 0666)
	if err != nil {
		return errors.Wrap(err, "WriteFile")
	}
	return nil
}

// LoadVariableValues loads the variables for a plugin.
func LoadVariableValues(pluginDir, installedPluginPath string) (map[string]interface{}, error) {
	filename := filepath.Join(pluginDir, installedPluginPath+variableJSONFileExt)
	f, err := os.Open(filename)
	if err != nil {
		if os.IsNotExist(err) {
			// no file - but not an error, just empty map
			return map[string]interface{}{}, nil
		}

View on GitHub (pinned to d624239058)

Solutions

  1. Sanitize values to plain JSON types (string, float64, bool, nil, []interface{}, map[string]interface{}) before calling SaveVariableValues
  2. Check errors.Cause(err) — it names the offending type, e.g. 'json: unsupported type: func()'; find and remove/convert that field
  3. Implement json.Marshaler on custom types you must persist, or convert to a serializable DTO first

Example fix

// before
values := map[string]interface{}{"callback": func() {}}
err := plugins.SaveVariableValues(dir, path, values) // json.MarshalIndent error
// after
values := map[string]interface{}{"name": cfg.Name, "timeout": cfg.Timeout.Seconds()}
if err := plugins.SaveVariableValues(dir, path, values); err != nil {
    return fmt.Errorf("save variable values: %w", err)
}
Defensive patterns

Strategy: validation

Validate before calling

func jsonSafe(v interface{}) error {
    var b []byte
    var err error
    if b, err = json.Marshal(v); err != nil {
        return err
    }
    _ = b
    return nil
}
// call: if err := jsonSafe(values); err != nil { /* sanitize values */ }

Try / catch

if err := plugins.SaveVariableValues(dir, path, values); err != nil {
    var ute *json.UnsupportedTypeError
    var uve *json.UnsupportedValueError
    if errors.As(errors.Cause(err), &ute) {
        log.Printf("cannot serialize type %v", ute.Type)
    } else if errors.As(errors.Cause(err), &uve) {
        log.Printf("cannot serialize value %v", uve.Value)
    }
    return err
}

Prevention

When it happens

Trigger: Calling plugins.SaveVariableValues(pluginDir, installedPluginPath, values) where values contains a non-JSON-serializable value, such as a func, chan, os.File, sync.Mutex embedded in a struct being marshaled, or math.NaN/Inf floats (which produce an error in Go's json on unsupported types; NaN/Inf actually error via 'unsupported value').

Common situations: Passing raw Go structures holding handles (DB connections, file objects) instead of plain data; storing time.T without proper handling in older patterns; values captured from reflection-based code; nested maps containing custom types without MarshalJSON.

Related errors


AI-assisted analysis of matryer/xbar@d624239058 (2026-09-02). Data as JSON: /api/errors/9704ce900ceb0d32. Report an issue: GitHub.