fatedier/frp · error
field "%s": cannot unmarshal %s into %s
Error message
field "%s": cannot unmarshal %s into %s
What it means
The line-less variant of the type-mismatch message: the same JSON UnmarshalTypeError reached the formatter but includeLine was false (or the field could not be located in the content), so only field path, offending value, and target type are reported. It means a config field has the wrong value type, without positional info.
Source
Thrown at pkg/config/load.go:243
}
var strictErr *toml.StrictMissingError
if errors.As(err, &strictErr) {
return strictErr
}
return err
}
// enhanceDecodeError tries to add field path and line number information to JSON/YAML decode errors.
func enhanceDecodeError(err error, originalContent []byte, includeLine bool) error {
var typeErr *json.UnmarshalTypeError
if errors.As(err, &typeErr) && typeErr.Field != "" {
if includeLine {
line := findFieldLineInContent(originalContent, typeErr.Field)
if line > 0 {
return fmt.Errorf("line %d: field \"%s\": cannot unmarshal %s into %s", line, typeErr.Field, typeErr.Value, typeErr.Type)
}
}
return fmt.Errorf("field \"%s\": cannot unmarshal %s into %s", typeErr.Field, typeErr.Value, typeErr.Type)
}
return err
}
// findFieldLineInContent searches the original config content for a field name
// and returns the 1-indexed line number where it appears, or 0 if not found.
func findFieldLineInContent(content []byte, fieldPath string) int {
if fieldPath == "" {
return 0
}
// Use the last component of the field path (e.g. "proxies" from "proxies" or
// "protocol" from "transport.protocol").
parts := strings.Split(fieldPath, ".")
searchKey := parts[len(parts)-1]
lines := bytes.Split(content, []byte("\n"))
for i, line := range lines {View on GitHub (pinned to 6c8a8d0a97)
Solutions
- Use the field path in the message to locate the value manually and fix its type
- If available, load the same content from a file so the line-number variant pinpoints the location
- Validate generated configs against frp's JSON schema before shipping
Example fix
// before
{ "transport": { "heartbeatInterval": "30" } }
// after
{ "transport": { "heartbeatInterval": 30 } } Defensive patterns
Strategy: validation
Validate before calling
// when assembling config programmatically, marshal+unmarshal through the typed struct first
probe := v1.ClientConfig{}
if err := json.Unmarshal(assembled, &probe); err != nil { var te *json.UnmarshalTypeError; if errors.As(err, &te) { return fmt.Errorf("field %s wrong type", te.Field) } } Try / catch
if err := json.Unmarshal(data, &cfg); err != nil { var te *json.UnmarshalTypeError; if errors.As(err, &te) && te.Field != "" { /* report field path to caller for manual location */ } } Prevention
- Generate configs from typed structs, not string concatenation
- Validate against the schema at generation time
- Log field paths in your config pipeline
When it happens
Trigger: Decoding frp config from sources where line mapping is not attempted — e.g. content assembled programmatically or strict-mode YAML paths that skip line lookup — and a field's value cannot be converted (string into int, object into array, etc.).
Common situations: Configs generated by tools/Helm templates that emit quoted numbers or wrong nesting; API-driven config assembly where the operator cannot rely on line numbers anyway.
Related errors
- line %d: field "%s": cannot unmarshal %s into %s
- failed to parse JSON: %w
- plugin type is empty
- visitor plugin type is empty
- toml: line %d, column %d: %s
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/11c30fcdfef144cd.
Report an issue: GitHub.