fatedier/frp · error
failed to marshal JSON: %w
Error message
failed to marshal JSON: %w
What it means
saveToFileUnlocked could not serialize the in-memory proxies/visitors into JSON (MarshalIndent failed) before writing. With the standard v1 configurers this is nearly impossible — it only fires if a ProxyConfigurer/VisitorConfigurer value carries a field type encoding/json cannot handle (func, chan) or implements MarshalJSON with a bug.
Source
Thrown at pkg/config/source/store.go:140
return nil
}
func (s *StoreSource) saveToFileUnlocked() error {
stored := storeData{
Proxies: make([]v1.TypedProxyConfig, 0, len(s.proxies)),
Visitors: make([]v1.TypedVisitorConfig, 0, len(s.visitors)),
}
for _, p := range s.proxies {
stored.Proxies = append(stored.Proxies, v1.TypedProxyConfig{ProxyConfigurer: p})
}
for _, v := range s.visitors {
stored.Visitors = append(stored.Visitors, v1.TypedVisitorConfig{VisitorConfigurer: v})
}
data, err := jsonx.MarshalIndent(stored, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal JSON: %w", err)
}
dir := filepath.Dir(s.config.Path)
if err := os.MkdirAll(dir, 0o755); err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
tmpPath := s.config.Path + ".tmp"
f, err := os.OpenFile(tmpPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
if err != nil {
return fmt.Errorf("failed to create temp file: %w", err)
}
if _, err := f.Write(data); err != nil {
f.Close()
os.Remove(tmpPath)
return fmt.Errorf("failed to write temp file: %w", err)View on GitHub (pinned to 6c8a8d0a97)
Solutions
- Unwrap the error chain to see the underlying marshal error (fmt %w preserves it)
- If a custom ProxyConfigurer/VisitorConfigurer was added, test its JSON marshaling in isolation: json.Marshal(cfg)
- Remove func/chan fields or fix the custom MarshalJSON implementation
- If no custom types are involved, report a bug with the full error and the config structs used
Example fix
// before: custom configurer with unsupported field
type MyProxy struct {
v1.ProxyBaseConfig
Handler func() `json:"-"` // ok if tagged, fails if not
}
// after: exclude non-serializable fields from JSON
type MyProxy struct {
v1.ProxyBaseConfig
Handler func() `json:"-"`
} Defensive patterns
Strategy: try-catch
Validate before calling
func canMarshal(v any) bool {
_, err := json.Marshal(v)
return err == nil
}
// call before AddProxy with a custom configurer:
// if !canMarshal(cfg) { /* reject early */ } Try / catch
if err := store.AddProxy(cfg); err != nil {
if strings.Contains(err.Error(), "failed to marshal JSON") {
// custom configurer is not JSON-serializable; do NOT retry, fix the type
}
} Prevention
- Unit-test custom ProxyConfigurer/VisitorConfigurer types for JSON round-trip before registering them
- Exclude func/chan fields with `json:"-"` tags in custom config structs
- Run `go vet` and linters that flag unmarshalable field types
When it happens
Trigger: Injecting a custom ProxyConfigurer implementation (via AddProxy) whose MarshalJSON returns an error or that contains a func/chan field; a config struct corrupted by an unsupported type assertion. Standard frp types never trigger this.
Common situations: Embedding frp as a library and registering custom proxy types with faulty custom marshalers; extremely rare in normal frp usage.
Related errors
- type is required
- failed to parse JSON: %w
- failed to decode proxy at index %d: %w
- proxy name cannot be empty
- failed to decode visitor at index %d: %w
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/49f95dd227284533.
Report an issue: GitHub.