lima-vm/lima · error

failed to marshal as JSON: %+v: %w

Error message

failed to marshal as JSON: %+v: %w

What it means

The "json" template function in textutil.TemplateFuncMap encodes its argument with encoding/json and panics if json.Encoder.Encode fails. Because text/template function panics propagate out of template execution, this error surfaces as a panic (recovered as an execution error by template.Execute) wrapped with the offending value and the underlying marshal error. JSON encoding of in-memory values essentially only fails for unsupported types (channels, funcs, complex numbers) or cyclic data.

Source

Thrown at pkg/textutil/textutil.go:62

	return PrefixString(prefix, text)
}

// MissingString returns message if the text is empty.
func MissingString(message, text string) string {
	if text == "" {
		return message
	}
	return text
}

// TemplateFuncMap is a text/template FuncMap.
var TemplateFuncMap = template.FuncMap{
	"json": func(v any) string {
		var b bytes.Buffer
		enc := json.NewEncoder(&b)
		enc.SetEscapeHTML(false)
		if err := enc.Encode(v); err != nil {
			panic(fmt.Errorf("failed to marshal as JSON: %+v: %w", v, err))
		}
		return strings.TrimSuffix(b.String(), "\n")
	},
	"yaml": func(v any) string {
		var b bytes.Buffer
		enc := yaml.NewEncoder(&b)
		if err := enc.Encode(v); err != nil {
			panic(fmt.Errorf("failed to marshal as YAML: %+v: %w", v, err))
		}
		return "---\n" + strings.TrimSuffix(b.String(), "\n")
	},
	"indent": func(a ...any) (string, error) {
		if len(a) == 0 {
			return "", errors.New("function takes at least one string argument")
		}
		if len(a) > 2 {
			return "", errors.New("function takes at most 2 arguments")
		}

View on GitHub (pinned to dd909d0973)

Solutions

  1. Inspect the wrapped error message to identify the unsupported field/value, then remove it or change its type before rendering (e.g. use a plain string or []byte instead of a func/chan field).
  2. Break data cycles: build an acyclic view (copy needed fields into a map/struct without back-pointers) before passing it to the template.
  3. Implement json.Marshaler on the offending type to emit a serializable representation.
  4. If you control the call site, prefer executing the template step inside a recover, or marshal the value yourself with json.Marshal and handle the error instead of using the panicking template func.

Example fix

// before (panics: Inst has a chan field)
tmpl.Execute(w, inst) // template: {{json .Inst}}
// after
view := map[string]any{"name": inst.Name, "dir": inst.Dir}
tmpl.Execute(w, view) // {{json .}} works
Defensive patterns

Strategy: try-catch

Validate before calling

if _, err := json.Marshal(v); err != nil {
    // sanitize v (drop funcs/chans, break cycles) before rendering the template
}

Type guard

func jsonSafe(v any) bool {
    switch v.(type) {
    case chan any, func(), complex64, complex128, map[any]any:
        return false
    }
    return true
}

Try / catch

defer func() {
    if r := recover(); r != nil {
        if strings.Contains(fmt.Sprint(r), "failed to marshal as JSON") {
            err = fmt.Errorf("template data not JSON-serializable: %v", r)
        }
    }
}()
b, err = textutil.ExecuteTemplate(tmpl, data)

Prevention

When it happens

Trigger: Executing a text/template (via textutil.ExecuteTemplate or any template using TemplateFuncMap) that calls `{{json v}}` where v contains a value json cannot encode: a channel, function, complex number, an invalid UTF-8 string (skipped, not an error), or most commonly a data cycle causing "json: unsupported value" / "encountered a cycle via ...".

Common situations: Passing a struct with func or chan fields into a template that emits it with {{json .}}; templates rendering Lima config objects that gained a non-serializable field; hand-built maps containing callbacks used inside provisioning templates.

Related errors


AI-assisted analysis of lima-vm/lima@dd909d0973 (2026-09-01). Data as JSON: /api/errors/754fa0f4afa64ae7. Report an issue: GitHub.