{"record":{"id":"043f65dcd4f2ab74","repo":"fatedier/frp","slug":"type-is-required","errorCode":null,"errorMessage":"type is required","messagePattern":"type is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/config/v1/decode.go","lineNumber":46,"sourceCode":"\nfunc decodeJSONWithOptions(b []byte, out any, options DecodeOptions) error {\n\treturn jsonx.UnmarshalWithOptions(b, out, jsonx.DecodeOptions{\n\t\tRejectUnknownMembers: options.DisallowUnknownFields,\n\t})\n}\n\nfunc isJSONNull(b []byte) bool {\n\treturn len(b) == 0 || string(b) == \"null\"\n}\n\ntype typedEnvelope struct {\n\tType   string           `json:\"type\"`\n\tPlugin jsonx.RawMessage `json:\"plugin,omitempty\"`\n}\n\nfunc DecodeProxyConfigurerJSON(b []byte, options DecodeOptions) (ProxyConfigurer, error) {\n\tif isJSONNull(b) {\n\t\treturn nil, errors.New(\"type is required\")\n\t}\n\n\tvar env typedEnvelope\n\tif err := jsonx.Unmarshal(b, &env); err != nil {\n\t\treturn nil, err\n\t}\n\n\tconfigurer := NewProxyConfigurerByType(ProxyType(env.Type))\n\tif configurer == nil {\n\t\treturn nil, fmt.Errorf(\"unknown proxy type: %s\", env.Type)\n\t}\n\tif err := decodeJSONWithOptions(b, configurer, options); err != nil {\n\t\treturn nil, fmt.Errorf(\"unmarshal ProxyConfig error: %v\", err)\n\t}\n\n\tif len(env.Plugin) > 0 && !isJSONNull(env.Plugin) {\n\t\tplugin, err := DecodeClientPluginOptionsJSON(env.Plugin, options)\n\t\tif err != nil {","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/config/v1/decode.go#L28-L64","documentation":"DecodeProxyConfigurerJSON (pkg/config/v1/decode.go:46) rejects a JSON null payload for a proxy configurer with 'type is required'. The decoder needs a typed envelope {\"type\": \"tcp\"|..., ...} to select the concrete config struct; null carries no type, so there is nothing to instantiate. (A present-but-unknown type instead yields 'unknown proxy type: ...'.)","triggerScenarios":"Decoding a proxy entry that is literal null — e.g. an API/store payload where a proxy slot was set to null, or json.Marshal of a nil pointer written into the store file then read back and decoded.","commonSituations":"Automation writing JSON null placeholders into proxy arrays; partial updates serialized from maps with nil values; hand-edited store/config JSON containing null entries.","solutions":["Send an object with a valid type field: {\"type\":\"tcp\",\"name\":\"ssh\",...}","Omit the entry entirely instead of serializing null","Filter null entries out of arrays before decoding","If a typed nil can reach the encoder, skip marshaling it (omitempty / nil check)"],"exampleFix":"// before\nraw := []byte(\"null\")\ncfg, err := v1.DecodeProxyConfigurerJSON(raw, opts) // type is required\n\n// after\nraw := []byte(`{\"type\":\"tcp\",\"name\":\"ssh\",\"remotePort\":22}`)\ncfg, err := v1.DecodeProxyConfigurerJSON(raw, opts)","handlingStrategy":"type-guard","validationCode":"if len(b) == 0 || string(b) == \"null\" {\n    return fmt.Errorf(\"proxy entry is null; an object with \\\"type\\\" is required\")\n}","typeGuard":"func isDecodableProxyJSON(b []byte) bool {\n    s := strings.TrimSpace(string(b))\n    return s != \"\" && s != \"null\"\n}","tryCatchPattern":"cfg, err := v1.DecodeProxyConfigurerJSON(b, opts)\nif err != nil {\n    if strings.Contains(err.Error(), \"type is required\") {\n        return nil, fmt.Errorf(\"null proxy config in payload at %s\", path)\n    }\n    return nil, err\n}","preventionTips":["Skip or reject null entries before decoding arrays of proxies","Use omitempty / nil checks when serializing so nulls never reach the store","Distinguish 'type is required' (null) from 'unknown proxy type' (bad type string)"],"tags":["go","configuration","json","decoding","frpc"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}