{"record":{"id":"49f95dd227284533","repo":"fatedier/frp","slug":"failed-to-marshal-json-w","errorCode":null,"errorMessage":"failed to marshal JSON: %w","messagePattern":"failed to marshal JSON: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/config/source/store.go","lineNumber":140,"sourceCode":"\treturn nil\n}\n\nfunc (s *StoreSource) saveToFileUnlocked() error {\n\tstored := storeData{\n\t\tProxies:  make([]v1.TypedProxyConfig, 0, len(s.proxies)),\n\t\tVisitors: make([]v1.TypedVisitorConfig, 0, len(s.visitors)),\n\t}\n\n\tfor _, p := range s.proxies {\n\t\tstored.Proxies = append(stored.Proxies, v1.TypedProxyConfig{ProxyConfigurer: p})\n\t}\n\tfor _, v := range s.visitors {\n\t\tstored.Visitors = append(stored.Visitors, v1.TypedVisitorConfig{VisitorConfigurer: v})\n\t}\n\n\tdata, err := jsonx.MarshalIndent(stored, \"\", \"  \")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to marshal JSON: %w\", err)\n\t}\n\n\tdir := filepath.Dir(s.config.Path)\n\tif err := os.MkdirAll(dir, 0o755); err != nil {\n\t\treturn fmt.Errorf(\"failed to create directory: %w\", err)\n\t}\n\n\ttmpPath := s.config.Path + \".tmp\"\n\n\tf, err := os.OpenFile(tmpPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create temp file: %w\", err)\n\t}\n\n\tif _, err := f.Write(data); err != nil {\n\t\tf.Close()\n\t\tos.Remove(tmpPath)\n\t\treturn fmt.Errorf(\"failed to write temp file: %w\", err)","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/config/source/store.go#L122-L158","documentation":"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.","triggerScenarios":"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.","commonSituations":"Embedding frp as a library and registering custom proxy types with faulty custom marshalers; extremely rare in normal frp usage.","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"],"exampleFix":"// before: custom configurer with unsupported field\ntype MyProxy struct {\n    v1.ProxyBaseConfig\n    Handler func() `json:\"-\"` // ok if tagged, fails if not\n}\n\n// after: exclude non-serializable fields from JSON\ntype MyProxy struct {\n    v1.ProxyBaseConfig\n    Handler func() `json:\"-\"`\n}","handlingStrategy":"try-catch","validationCode":"func canMarshal(v any) bool {\n\t_, err := json.Marshal(v)\n\treturn err == nil\n}\n\n// call before AddProxy with a custom configurer:\n// if !canMarshal(cfg) { /* reject early */ }","typeGuard":null,"tryCatchPattern":"if err := store.AddProxy(cfg); err != nil {\n\tif strings.Contains(err.Error(), \"failed to marshal JSON\") {\n\t\t// custom configurer is not JSON-serializable; do NOT retry, fix the type\n\t}\n}","preventionTips":["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"],"tags":["json","serialization","custom-types","go"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}